Skip to content

i18n: lazy-load translated names when available#794

Open
bubelov wants to merge 1 commit intomainfrom
localized-names
Open

i18n: lazy-load translated names when available#794
bubelov wants to merge 1 commit intomainfrom
localized-names

Conversation

@bubelov
Copy link
Collaborator

@bubelov bubelov commented Mar 6, 2026

Note: locales are not limited to hardcoded ones, so it ingnores any website-scoped user preferences and relies on browser preferences instead.

If there is a way of getting user overrides, we can try user-set locales first, but I'm not sure how to do it in a clean way and if it's reliable, I suspect it can default to returning en for non-whitelisted languages, which is not optimal.

Summary by CodeRabbit

  • New Features
    • Added localization support for merchant names, displaying them in the user's preferred language where available
    • Merchant details across the platform—including titles, headings, and location information—now reflect localized names when applicable

@netlify
Copy link

netlify bot commented Mar 6, 2026

Deploy Preview for btcmap ready!

Name Link
🔨 Latest commit a141c33
🔍 Latest deploy log https://app.netlify.com/projects/btcmap/deploys/69aa59f1cc739b0008fbecce
😎 Deploy Preview https://deploy-preview-794--btcmap.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 55 (🔴 down 38 from production)
Accessibility: 97 (no change from production)
Best Practices: 92 (🔴 down 8 from production)
SEO: 99 (no change from production)
PWA: 90 (no change from production)
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@qodo-code-review
Copy link
Contributor

Review Summary by Qodo

Lazy-load localized merchant names from API

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Lazy-load localized merchant names based on browser language preference
• Fetch translated names from BTC Map API when available
• Display localized names in page title, heading, and section headers
• Gracefully fall back to default name if translation unavailable
Diagram
flowchart LR
  A["Browser Language"] -->|Check if not EN| B["Fetch Localized Name"]
  B -->|API Call| C["BTC Map API"]
  C -->|Response| D{Name Different?}
  D -->|Yes| E["Use Localized Name"]
  D -->|No| F["Use Default Name"]
  E --> G["Display in UI"]
  F --> G
Loading

Grey Divider

File Changes

1. src/routes/merchant/[id]/+page.svelte ✨ Enhancement +37/-8

Add lazy-loaded localized merchant names

• Added localizedName and localizedNameFetched reactive variables to store fetched translations
• Implemented fetchLocalizedName() async function that queries BTC Map API for language-specific
 merchant names
• Added reactive block to trigger localized name fetch based on navigator.language (skips English)
• Updated page title, og:title, and twitter:title meta tags to prioritize localized names
• Updated merchant heading and section headers to display localized names with fallback chain
• Removed unused og:image meta tag line

src/routes/merchant/[id]/+page.svelte


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Contributor

qodo-code-review bot commented Mar 6, 2026

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (2) 📎 Requirement gaps (0)

Grey Divider


Action required

1. localizedNameFetched unused variable 📘 Rule violation ✓ Correctness
Description
The PR introduces localizedNameFetched but it is never read, which commonly triggers
no-unused-vars lint errors. This can cause yarn run lint to fail.
Code

src/routes/merchant/[id]/+page.svelte[R140-147]

+let localizedNameFetched = false;
+
+$: {
+	const lang = navigator.language;
+	if (lang !== "en") {
+		fetchLocalizedName(data.id, data.name, lang);
+	}
+	localizedNameFetched = true;
Evidence
PR Compliance ID 3 requires linting to pass. The added code declares localizedNameFetched and
assigns to it, but does not use it anywhere, which is a typical lint failure condition.

AGENTS.md
src/routes/merchant/[id]/+page.svelte[140-147]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`localizedNameFetched` is declared and assigned but never read, which can cause lint failures (unused variable).

## Issue Context
The variable appears intended to prevent repeated fetches from the reactive block, but it currently does not affect control flow.

## Fix Focus Areas
- src/routes/merchant/[id]/+page.svelte[140-147]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. SSR crash on navigator 🐞 Bug ⛯ Reliability
Description
The new reactive block reads navigator.language unconditionally, which will throw during SSR and
break rendering of the merchant page. This component already uses browser guards elsewhere, so
this is a high-confidence regression.
Code

src/routes/merchant/[id]/+page.svelte[R142-146]

+$: {
+	const lang = navigator.language;
+	if (lang !== "en") {
+		fetchLocalizedName(data.id, data.name, lang);
+	}
Evidence
navigator.language is accessed inside a $: reactive block without a browser/server guard;
SvelteKit runs component initialization/reactive statements during SSR, where navigator is
undefined. The same file imports browser and correctly guards other browser-only work in
onMount, indicating SSR is expected here.

src/routes/merchant/[id]/+page.svelte[139-148]
src/routes/merchant/[id]/+page.svelte[48-54]
src/routes/merchant/[id]/+page.svelte[279-298]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Merchant page SSR can crash because a `$:` reactive block reads `navigator.language` unconditionally.

## Issue Context
SvelteKit renders this component during SSR. Browser-only APIs (`navigator`) must be guarded.

## Fix Focus Areas
- src/routes/merchant/[id]/+page.svelte[139-148]

### Suggested change
Wrap the reactive block with a `browser` check (already imported in this file), e.g.:
- `if (!browser) return;`
- then read `navigator.language` and trigger the fetch.

Alternatively: move the localized-name fetch into `onMount` so it never runs on the server.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Racy/stale localized name 🐞 Bug ✓ Correctness
Description
Client-side navigation between merchant IDs can briefly show the previous merchant’s localized name,
and a slower previous fetch can overwrite the current merchant name due to missing reset and missing
request-generation guard. This affects title/H1 and can confuse users.
Code

src/routes/merchant/[id]/+page.svelte[R142-163]

+$: {
+	const lang = navigator.language;
+	if (lang !== "en") {
+		fetchLocalizedName(data.id, data.name, lang);
+	}
+	localizedNameFetched = true;
+}
+
+async function fetchLocalizedName(
+	placeId: string,
+	defaultName: string | undefined,
+	lang: string,
+) {
+	try {
+		const response = await fetch(
+			`https://api.btcmap.org/v4/places/${placeId}?fields=name&lang=${lang}`,
+		);
+		if (!response.ok) return;
+		const data = await response.json();
+		if (data.name && data.name !== defaultName) {
+			localizedName = data.name;
+		}
Evidence
The merchant page sets localizedName only when a fetch resolves and never resets it when data.id
changes; there is also no request-id/generation check, so results from older requests can win. The
repo already uses a requestId pattern to prevent stale async updates and resets the displayed name
before fetching.

src/routes/merchant/[id]/+page.svelte[142-163]
src/routes/countries/[section]/components/CountryCard.svelte[11-23]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`localizedName` can become stale and async results can race when navigating between different merchant IDs in the same route component instance.

## Issue Context
SvelteKit can reuse `+page.svelte` instances across parameter changes. Without resetting state and guarding async updates, an older fetch can overwrite newer data.

## Fix Focus Areas
- src/routes/merchant/[id]/+page.svelte[139-167]

### Suggested approach
- Introduce a request generation counter (similar to `CountryCard.svelte`).
- In the reactive block triggered by `data.id` / `data.name`, set `localizedName = undefined` (or `localizedName = data.name`) before kicking off the fetch.
- When the fetch resolves, only apply the result if the generation id matches the latest one (or `placeId === data.id` at time of resolution).
- Optionally use `AbortController` to cancel in-flight requests when `data.id` changes.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. <title> indentation inconsistent 📘 Rule violation ✓ Correctness
Description
The new <title> line in <svelte:head> appears mis-indented compared to surrounding lines, which
may indicate formatting that would change after running the formatter. This risks violating the
formatting requirement.
Code

src/routes/merchant/[id]/+page.svelte[R335-337]

+<title>{localizedName || name ? (localizedName || name) + ' - ' : ''}BTC Map - {$_('meta.merchant')}</title>
+	<meta property="og:title" content="{localizedName || name ? (localizedName || name) + ' - ' : ''}BTC Map - {$_('meta.merchant')}" />
+	<meta name="twitter:title" content="{localizedName || name ? (localizedName || name) + ' - ' : ''}BTC Map - {$_('meta.merchant')}" />
Evidence
PR Compliance ID 1 requires all changed files to be formatted with the project formatter. The
<title> line is not aligned with adjacent head elements, suggesting a formatter would produce a
diff.

AGENTS.md
src/routes/merchant/[id]/+page.svelte[335-337]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `&lt;title&gt;` line appears inconsistently indented compared to adjacent `&lt;meta&gt;` tags, which may indicate formatting drift.

## Issue Context
The repository expects formatting to be normalized via `yarn run format:fix`.

## Fix Focus Areas
- src/routes/merchant/[id]/+page.svelte[335-337]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Extra fetch for en-US 🐞 Bug ➹ Performance
Description
The check lang !== 'en' will be true for English variants like en-US/en-GB, causing an
unnecessary API call for many English users. This is wasteful and increases latency/network load.
Code

src/routes/merchant/[id]/+page.svelte[R143-146]

+	const lang = navigator.language;
+	if (lang !== "en") {
+		fetchLocalizedName(data.id, data.name, lang);
+	}
Evidence
The merchant page compares navigator.language via strict equality to "en". Elsewhere in the
codebase, locale detection uses prefix checks (startsWith) because browser language commonly
includes a region subtag.

src/routes/merchant/[id]/+page.svelte[142-146]
src/lib/i18n/index.ts[18-29]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
English browsers often report `navigator.language` as `en-US` / `en-GB`, so `lang !== &#x27;en&#x27;` triggers unnecessary localized-name fetches.

## Issue Context
This is a performance/network inefficiency; it does not necessarily break correctness.

## Fix Focus Areas
- src/routes/merchant/[id]/+page.svelte[142-147]

### Suggested change
- Compute a normalized language decision key, e.g.:
 - `const lang = navigator.language;`
 - `const isEnglish = lang?.toLowerCase().startsWith(&#x27;en&#x27;);`
 - `if (!isEnglish) fetchLocalizedName(...);`

Keep passing the full `lang` value to the API if that’s desired, but avoid the fetch for English variants.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. og:image tag removed 🐞 Bug ✓ Correctness
Description
The merchant page head no longer includes <meta property="og:image">, which can degrade link
previews on platforms that rely on OpenGraph (e.g., Facebook/LinkedIn). The rest of the app appears
to include both og:image and twitter:image.
Code

src/routes/merchant/[id]/+page.svelte[R334-339]

<svelte:head>
-	<title>{name ? name + ' - ' : ''}BTC Map - {$_('meta.merchant')}</title>
-	<meta property="og:image" content={ogImage} />
-	<meta property="og:title" content="{name ? name + ' - ' : ''}BTC Map - {$_('meta.merchant')}" />
-	<meta name="twitter:title" content="{name ? name + ' - ' : ''}BTC Map - {$_('meta.merchant')}" />
+<title>{localizedName || name ? (localizedName || name) + ' - ' : ''}BTC Map - {$_('meta.merchant')}</title>
+	<meta property="og:title" content="{localizedName || name ? (localizedName || name) + ' - ' : ''}BTC Map - {$_('meta.merchant')}" />
+	<meta name="twitter:title" content="{localizedName || name ? (localizedName || name) + ' - ' : ''}BTC Map - {$_('meta.merchant')}" />
	<meta name="twitter:image" content={ogImage} />
</svelte:head>
Evidence
The merchant page head now sets only og:title and Twitter tags; it does not set og:image. Other
routes consistently provide both og:image and twitter:image, suggesting this was unintended or
at least inconsistent.

src/routes/merchant/[id]/+page.svelte[334-339]
src/routes/community/[area]/[section]/+page.svelte[19-25]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Merchant page is missing the OpenGraph image tag (`og:image`), which may break previews on OpenGraph consumers.

## Issue Context
Other routes include both `og:image` and `twitter:image`, but merchant now only includes the Twitter image.

## Fix Focus Areas
- src/routes/merchant/[id]/+page.svelte[334-339]

### Suggested change
Add back:
- `&lt;meta property=&quot;og:image&quot; content={ogImage} /&gt;`
near the other OG tags.

If the removal was intentional, document why merchant differs from other routes.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@coderabbitai
Copy link

coderabbitai bot commented Mar 6, 2026

📝 Walkthrough

Walkthrough

This pull request adds localization support for merchant names by fetching a localized version when the page loads (non-English), then replacing all usages of the original merchant name throughout the page with the localized variant, falling back to the original name if the fetch fails.

Changes

Cohort / File(s) Summary
Merchant Localization
src/routes/merchant/[id]/+page.svelte
Introduces a localizedName state, fetchLocalizedName function, and reactive block that fetches localized merchant names on page load (excluding English). Replaces all merchant name references in document titles, Open Graph/Twitter metadata, headings, and UI sections with the localized name when available, with fallback to original name.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • #749: Modifies the same merchant detail page for page metadata and title using i18n keys.
  • #677: Updates the same file to convert strings to i18n keys for localization support.

Suggested labels

localization, feature, merchant, Review effort 4/5

Suggested reviewers

  • escapedcat
  • dadofsambonzuki

Poem

🐰 A merchant's name across the lands so wide,
Fetched and localized with Svelte-born pride,
English, French, or tongue unknown,
Each traveler finds a name like home! 🌍

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is largely incomplete, missing critical sections from the template (issue reference, change description, screenshots, context) and only provides implementation notes about locale handling limitations. Complete the PR description by adding: issue reference, clear description of the localization implementation, any relevant screenshots, and additional technical context about the lazy-loading approach and browser preference handling.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: implementing lazy-loading of translated/localized merchant names based on language availability.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch localized-names

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review
Copy link
Contributor

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: e2e-tests

Failed stage: Run Playwright tests [❌]

Failed test name: tests/boost-invoice.spec.ts:8:2 › Boost Invoice Generation › generates valid invoice through complete UI flow

Failure summary:

The action failed because Playwright end-to-end tests returned a non-zero exit code (exit code 1)
after multiple test failures.

Primary underlying issue:
- The SvelteKit SSR web server is throwing runtime errors on merchant
pages: ReferenceError: navigator is not defined, producing HTTP 500 responses (repeated for
/merchant/23143 and /merchant/7361). The stack trace points to
.svelte-kit/output/server/entries/pages/merchant/id/_page.svelte.js:170:22, which indicates
browser-only code (navigator) is being executed during server-side rendering.

Resulting test failures:
- tests/map-drawer.spec.ts timed out waiting for an API response:
page.waitForResponse: Timeout 10000ms exceeded at tests/map-drawer.spec.ts:287:15 and
tests/map-drawer.spec.ts:248:15, likely because the server errors prevented the expected network
responses/UI updates.
- tests/boost-invoice.spec.ts failed because expected merchant UI never
rendered: getByRole('heading', { name: 'Green Town', exact: true }) was not found/visible at
tests/boost-invoice.spec.ts:28:80.
- tests/deleted-merchant.spec.ts failed because the page returned
500 instead of the expected 200 at tests/deleted-merchant.spec.ts:24:30, and subsequent UI
assertions failed (deleted notice banner, heading with Deleted suffix, and map container) due to the
page not rendering after server errors (tests/deleted-merchant.spec.ts:32:5, :44:5, :51:52).

Additional (earlier) build warning:
- A dependency mismatch is reported during bundling: "untrack",
"fork", and "settled" are not exported by svelte/src/runtime/ssr.js / svelte/src/runtime/index.js
but are imported by @sveltejs/kit (node_modules/@sveltejs/kit/src/runtime/client/client.js). This
suggests a svelte / @sveltejs/kit version incompatibility, which may contribute to instability,
though the immediate test failures are driven by the SSR navigator error and resulting 500s.

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

183:  NODE_OPTIONS: --max-old-space-size=4096
184:  ##[endgroup]
185:  �[93m➤�[39m YN0000: Yarn detected that the current workflow is executed from a public pull request. For safety the hardened mode has been enabled.
186:  �[93m➤�[39m YN0000: It will prevent malicious lockfile manipulations, in exchange for a slower install time. You can opt-out if necessary; check our documentation for more details.
187:  �[94m➤�[39m YN0000: · Yarn 4.12.0
188:  �[94m➤�[39m �[90mYN0000�[39m: ┌ Resolution step
189:  ##[group]Resolution step
190:  ##[endgroup]
191:  �[94m➤�[39m �[90mYN0000�[39m: └ Completed in 6s 164ms
192:  �[94m➤�[39m �[90mYN0000�[39m: ┌ Fetch step
193:  ##[group]Fetch step
194:  ##[endgroup]
195:  �[94m➤�[39m �[90mYN0000�[39m: └ Completed in 0s 528ms
196:  �[94m➤�[39m �[90mYN0000�[39m: ┌ Link step
197:  ##[group]Link step
198:  �[94m➤�[39m YN0007: │ �[38;5;173mesbuild�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.19.12�[39m must be built because it never has been before or the last one failed
199:  �[94m➤�[39m YN0007: │ �[38;5;173msvelte-preprocess�[39m�[38;5;111m@�[39m�[38;5;111mnpm:6.0.3 [cec52]�[39m must be built because it never has been before or the last one failed
200:  �[94m➤�[39m YN0007: │ �[38;5;173mesbuild�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.21.5�[39m must be built because it never has been before or the last one failed
201:  �[94m➤�[39m YN0007: │ �[38;5;173mesbuild�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.25.12�[39m must be built because it never has been before or the last one failed
202:  �[94m➤�[39m YN0007: │ �[38;5;173mesbuild�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.25.10�[39m must be built because it never has been before or the last one failed
203:  �[94m➤�[39m YN0007: │ �[38;5;173mes5-ext�[39m�[38;5;111m@�[39m�[38;5;111mnpm:0.10.64�[39m must be built because it never has been before or the last one failed
204:  �[94m➤�[39m �[90mYN0000�[39m: │ �[38;5;173msvelte-preprocess�[39m�[38;5;111m@�[39m�[38;5;111mnpm:6.0.3 [cec52]�[39m �[32mSTDOUT�[39m [svelte-preprocess] Don't forget to install the preprocessors packages that will be used: sass, stylus, less, postcss & postcss-load-config, coffeescript, pug, etc...
...

1446:  [WebServer] 
1447:  [WebServer] node_modules/@sveltejs/kit/src/runtime/client/client.js (6:23): "untrack" is not exported by "node_modules/svelte/src/runtime/ssr.js", imported by "node_modules/@sveltejs/kit/src/runtime/client/client.js".
1448:  [WebServer] node_modules/@sveltejs/kit/src/runtime/client/client.js (333:26): "fork" is not exported by "node_modules/svelte/src/runtime/ssr.js", imported by "node_modules/@sveltejs/kit/src/runtime/client/client.js".
1449:  [WebServer] node_modules/@sveltejs/kit/src/runtime/client/client.js (1176:30): "settled" is not exported by "node_modules/svelte/src/runtime/ssr.js", imported by "node_modules/@sveltejs/kit/src/runtime/client/client.js".
1450:  [WebServer] node_modules/@sveltejs/kit/src/runtime/client/client.js (5:23): "untrack" is not exported by "node_modules/svelte/src/runtime/index.js", imported by "node_modules/@sveltejs/kit/src/runtime/client/client.js".
1451:  [WebServer] node_modules/@sveltejs/kit/src/runtime/client/client.js (332:26): "fork" is not exported by "node_modules/svelte/src/runtime/index.js", imported by "node_modules/@sveltejs/kit/src/runtime/client/client.js".
1452:  [WebServer] node_modules/@sveltejs/kit/src/runtime/client/client.js (1175:30): "settled" is not exported by "node_modules/svelte/src/runtime/index.js", imported by "node_modules/@sveltejs/kit/src/runtime/client/client.js".
1453:  [WebServer] �[33m
1454:  [WebServer] (!) Some chunks are larger than 500 kB after minification. Consider:
1455:  [WebServer] - Using dynamic import() to code-split the application
1456:  [WebServer] - Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks
1457:  [WebServer] - Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.�[39m
1458:  Running 42 tests using 3 workers
1459:  [WebServer] 
1460:  [WebServer] �[1;31m[500] GET /merchant/23143�[0m
1461:  [WebServer] ReferenceError: navigator is not defined
1462:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/merchant/_id_/_page.svelte.js:170:22
1463:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1464:  [WebServer]     at Object.default (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:90:97)
1465:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/_layout.svelte.js:250:37
1466:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1467:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:42:121
1468:  [WebServer]     at $$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1469:  [WebServer]     at Object.render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:381:20)
1470:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/index.js:1549:45
1471:  [WebServer]     at AsyncLocalStorage.run (node:async_hooks:346:14)
1472:  [WebServer] 
1473:  [WebServer] �[1;31m[500] GET /merchant/23143�[0m
1474:  [WebServer] ReferenceError: navigator is not defined
1475:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/merchant/_id_/_page.svelte.js:170:22
1476:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1477:  [WebServer]     at Object.default (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:90:97)
1478:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/_layout.svelte.js:250:37
1479:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1480:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:42:121
1481:  [WebServer]     at $$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1482:  [WebServer]     at Object.render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:381:20)
1483:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/index.js:1549:45
1484:  [WebServer]     at AsyncLocalStorage.run (node:async_hooks:346:14)
1485:  [WebServer] 
1486:  [WebServer] �[1;31m[500] GET /merchant/7361�[0m
1487:  [WebServer] ReferenceError: navigator is not defined
1488:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/merchant/_id_/_page.svelte.js:170:22
1489:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1490:  [WebServer]     at Object.default (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:90:97)
1491:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/_layout.svelte.js:250:37
1492:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1493:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:42:121
1494:  [WebServer]     at $$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1495:  [WebServer]     at Object.render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:381:20)
1496:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/index.js:1549:45
1497:  [WebServer]     at AsyncLocalStorage.run (node:async_hooks:346:14)
1498:  [WebServer] 
1499:  [WebServer] �[1;31m[500] GET /merchant/7361�[0m
1500:  [WebServer] ReferenceError: navigator is not defined
1501:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/merchant/_id_/_page.svelte.js:170:22
1502:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1503:  [WebServer]     at Object.default (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:90:97)
1504:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/_layout.svelte.js:250:37
1505:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1506:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:42:121
1507:  [WebServer]     at $$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1508:  [WebServer]     at Object.render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:381:20)
1509:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/index.js:1549:45
1510:  [WebServer]     at AsyncLocalStorage.run (node:async_hooks:346:14)
1511:  [WebServer] 
1512:  [WebServer] �[1;31m[500] GET /merchant/7361�[0m
1513:  [WebServer] ReferenceError: navigator is not defined
1514:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/merchant/_id_/_page.svelte.js:170:22
1515:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1516:  [WebServer]     at Object.default (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:90:97)
1517:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/_layout.svelte.js:250:37
1518:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1519:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:42:121
1520:  [WebServer]     at $$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1521:  [WebServer]     at Object.render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:381:20)
1522:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/index.js:1549:45
1523:  [WebServer]     at AsyncLocalStorage.run (node:async_hooks:346:14)
1524:  [WebServer] 
1525:  [WebServer] �[1;31m[500] GET /merchant/7361�[0m
1526:  [WebServer] ReferenceError: navigator is not defined
1527:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/merchant/_id_/_page.svelte.js:170:22
1528:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1529:  [WebServer]     at Object.default (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:90:97)
1530:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/_layout.svelte.js:250:37
1531:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1532:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:42:121
1533:  [WebServer]     at $$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1534:  [WebServer]     at Object.render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:381:20)
1535:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/index.js:1549:45
1536:  [WebServer]     at AsyncLocalStorage.run (node:async_hooks:346:14)
1537:  [WebServer] 
1538:  [WebServer] �[1;31m[500] GET /merchant/23143�[0m
1539:  [WebServer] ReferenceError: navigator is not defined
1540:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/merchant/_id_/_page.svelte.js:170:22
1541:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1542:  [WebServer]     at Object.default (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:90:97)
1543:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/_layout.svelte.js:250:37
1544:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1545:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:42:121
1546:  [WebServer]     at $$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1547:  [WebServer]     at Object.render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:381:20)
1548:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/index.js:1549:45
1549:  [WebServer]     at AsyncLocalStorage.run (node:async_hooks:346:14)
1550:  [WebServer] 
1551:  [WebServer] �[1;31m[500] GET /merchant/7361�[0m
1552:  [WebServer] ReferenceError: navigator is not defined
1553:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/merchant/_id_/_page.svelte.js:170:22
1554:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1555:  [WebServer]     at Object.default (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:90:97)
1556:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/_layout.svelte.js:250:37
1557:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1558:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:42:121
1559:  [WebServer]     at $$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1560:  [WebServer]     at Object.render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:381:20)
1561:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/index.js:1549:45
1562:  [WebServer]     at AsyncLocalStorage.run (node:async_hooks:346:14)
1563:  [WebServer] 
1564:  [WebServer] �[1;31m[500] GET /merchant/7361�[0m
1565:  [WebServer] ReferenceError: navigator is not defined
1566:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/merchant/_id_/_page.svelte.js:170:22
1567:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1568:  [WebServer]     at Object.default (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:90:97)
1569:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/_layout.svelte.js:250:37
1570:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1571:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:42:121
1572:  [WebServer]     at $$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1573:  [WebServer]     at Object.render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:381:20)
1574:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/index.js:1549:45
1575:  [WebServer]     at AsyncLocalStorage.run (node:async_hooks:346:14)
1576:  [WebServer] 
1577:  [WebServer] �[1;31m[500] GET /merchant/7361�[0m
1578:  [WebServer] ReferenceError: navigator is not defined
1579:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/merchant/_id_/_page.svelte.js:170:22
1580:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1581:  [WebServer]     at Object.default (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:90:97)
1582:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/_layout.svelte.js:250:37
1583:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1584:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:42:121
1585:  [WebServer]     at $$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1586:  [WebServer]     at Object.render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:381:20)
1587:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/index.js:1549:45
1588:  [WebServer]     at AsyncLocalStorage.run (node:async_hooks:346:14)
1589:  [WebServer] 
1590:  [WebServer] �[1;31m[500] GET /merchant/7361�[0m
1591:  [WebServer] ReferenceError: navigator is not defined
1592:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/merchant/_id_/_page.svelte.js:170:22
1593:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1594:  [WebServer]     at Object.default (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:90:97)
1595:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/_layout.svelte.js:250:37
1596:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1597:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:42:121
1598:  [WebServer]     at $$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1599:  [WebServer]     at Object.render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:381:20)
1600:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/index.js:1549:45
1601:  [WebServer]     at AsyncLocalStorage.run (node:async_hooks:346:14)
1602:  [WebServer] 
1603:  [WebServer] �[1;31m[500] GET /merchant/7361�[0m
1604:  [WebServer] ReferenceError: navigator is not defined
1605:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/merchant/_id_/_page.svelte.js:170:22
1606:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1607:  [WebServer]     at Object.default (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:90:97)
1608:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/_layout.svelte.js:250:37
1609:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1610:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:42:121
1611:  [WebServer]     at $$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1612:  [WebServer]     at Object.render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:381:20)
1613:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/index.js:1549:45
1614:  [WebServer]     at AsyncLocalStorage.run (node:async_hooks:346:14)
1615:  [WebServer] 
1616:  [WebServer] �[1;31m[500] GET /merchant/7361�[0m
1617:  [WebServer] ReferenceError: navigator is not defined
1618:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/merchant/_id_/_page.svelte.js:170:22
1619:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1620:  [WebServer]     at Object.default (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:90:97)
1621:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/_layout.svelte.js:250:37
1622:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1623:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:42:121
1624:  [WebServer]     at $$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1625:  [WebServer]     at Object.render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:381:20)
1626:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/index.js:1549:45
1627:  [WebServer]     at AsyncLocalStorage.run (node:async_hooks:346:14)
1628:  [WebServer] 
1629:  [WebServer] �[1;31m[500] GET /merchant/7361�[0m
1630:  [WebServer] ReferenceError: navigator is not defined
1631:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/merchant/_id_/_page.svelte.js:170:22
1632:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1633:  [WebServer]     at Object.default (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:90:97)
1634:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/_layout.svelte.js:250:37
1635:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1636:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:42:121
1637:  [WebServer]     at $$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1638:  [WebServer]     at Object.render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:381:20)
1639:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/index.js:1549:45
1640:  [WebServer]     at AsyncLocalStorage.run (node:async_hooks:346:14)
1641:  ··········×·····×·××F×F×××F··F×··Found merchant link: /merchant/19291
1642:  API response wait failed: page.waitForResponse: Timeout 10000ms exceeded while waiting for event "response"
1643:  at �[90m/home/runner/work/btcmap.org/btcmap.org/�[39mtests/map-drawer.spec.ts:287:15 {
1644:  name: �[32m'TimeoutError'�[39m,
1645:  [�[32mSymbol(step)�[39m]: {
...

1655:  group: �[90mundefined�[39m,
1656:  stepId: �[32m'pw:api@33'�[39m,
1657:  boxedStack: �[90mundefined�[39m,
1658:  steps: [],
1659:  attachmentIndices: [],
1660:  info: TestStepInfoImpl {
1661:  annotations: [],
1662:  _testInfo: �[36m[TestInfoImpl]�[39m,
1663:  _stepId: �[32m'pw:api@33'�[39m,
1664:  _title: �[32m'Wait for event "response"'�[39m,
1665:  _parentStep: �[90mundefined�[39m,
1666:  skip: �[36m[Function (anonymous)]�[39m
1667:  },
1668:  complete: �[36m[Function: complete]�[39m,
1669:  endWallTime: �[33m1772771963123�[39m,
1670:  error: {
1671:  message: �[32m'TimeoutError: page.waitForResponse: Timeout 10000ms exceeded while waiting for event "response"'�[39m,
1672:  stack: �[32m'TimeoutError: page.waitForResponse: Timeout 10000ms exceeded while waiting for event "response"\n'�[39m +
1673:  �[32m'    at /home/runner/work/btcmap.org/btcmap.org/tests/map-drawer.spec.ts:287:15'�[39m,
1674:  cause: �[90mundefined�[39m
1675:  }
1676:  }
1677:  }
1678:  API response wait failed: page.waitForResponse: Timeout 10000ms exceeded while waiting for event "response"
1679:  at �[90m/home/runner/work/btcmap.org/btcmap.org/�[39mtests/map-drawer.spec.ts:248:15 {
1680:  name: �[32m'TimeoutError'�[39m,
1681:  [�[32mSymbol(step)�[39m]: {
...

1691:  group: �[90mundefined�[39m,
1692:  stepId: �[32m'pw:api@32'�[39m,
1693:  boxedStack: �[90mundefined�[39m,
1694:  steps: [],
1695:  attachmentIndices: [],
1696:  info: TestStepInfoImpl {
1697:  annotations: [],
1698:  _testInfo: �[36m[TestInfoImpl]�[39m,
1699:  _stepId: �[32m'pw:api@32'�[39m,
1700:  _title: �[32m'Wait for event "response"'�[39m,
1701:  _parentStep: �[90mundefined�[39m,
1702:  skip: �[36m[Function (anonymous)]�[39m
1703:  },
1704:  complete: �[36m[Function: complete]�[39m,
1705:  endWallTime: �[33m1772771963309�[39m,
1706:  error: {
1707:  message: �[32m'TimeoutError: page.waitForResponse: Timeout 10000ms exceeded while waiting for event "response"'�[39m,
1708:  stack: �[32m'TimeoutError: page.waitForResponse: Timeout 10000ms exceeded while waiting for event "response"\n'�[39m +
1709:  �[32m'    at /home/runner/work/btcmap.org/btcmap.org/tests/map-drawer.spec.ts:248:15'�[39m,
1710:  cause: �[90mundefined�[39m
1711:  }
1712:  }
1713:  }
1714:  [WebServer] 
1715:  [WebServer] �[1;31m[500] GET /merchant/7361�[0m
1716:  [WebServer] ReferenceError: navigator is not defined
1717:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/merchant/_id_/_page.svelte.js:170:22
1718:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1719:  [WebServer]     at Object.default (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:90:97)
1720:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/entries/pages/_layout.svelte.js:250:37
1721:  [WebServer]     at Object.$$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1722:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/internal.js:42:121
1723:  [WebServer]     at $$render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:373:18)
1724:  [WebServer]     at Object.render (file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/chunks/ssr.js:381:20)
1725:  [WebServer]     at file:///home/runner/work/btcmap.org/btcmap.org/.svelte-kit/output/server/index.js:1549:45
1726:  [WebServer]     at AsyncLocalStorage.run (node:async_hooks:346:14)
1727:  ···×··············F
1728:  1) [chromium] › tests/boost-invoice.spec.ts:8:2 › Boost Invoice Generation › generates valid invoice through complete UI flow 
1729:  Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoBeVisible�[2m(�[22m�[2m)�[22m failed
1730:  Locator: getByRole('heading', { name: 'Green Town', exact: true })
1731:  Expected: visible
1732:  Timeout: 5000ms
1733:  Error: element(s) not found
1734:  Call log:
1735:  �[2m  - Expect "toBeVisible" with timeout 5000ms�[22m
1736:  �[2m  - waiting for getByRole('heading', { name: 'Green Town', exact: true })�[22m
1737:  26 |
1738:  27 | 		// Wait for merchant data to load completely
1739:  > 28 | 		await expect(page.getByRole('heading', { name: 'Green Town', exact: true })).toBeVisible();
1740:  | 		                                                                             ^
1741:  29 | 		await expect(page.getByText('Last Surveyed')).toBeVisible();
1742:  30 |
1743:  31 | 		// Wait for boost button to be interactive
1744:  at /home/runner/work/btcmap.org/btcmap.org/tests/boost-invoice.spec.ts:28:80
1745:  Error Context: test-results/boost-invoice-Boost-Invoic-f5e10-ce-through-complete-UI-flow-chromium/error-context.md
1746:  Retry #1 ───────────────────────────────────────────────────────────────────────────────────────
1747:  Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoBeVisible�[2m(�[22m�[2m)�[22m failed
1748:  Locator: getByRole('heading', { name: 'Green Town', exact: true })
1749:  Expected: visible
1750:  Timeout: 5000ms
1751:  Error: element(s) not found
1752:  Call log:
1753:  �[2m  - Expect "toBeVisible" with timeout 5000ms�[22m
1754:  �[2m  - waiting for getByRole('heading', { name: 'Green Town', exact: true })�[22m
1755:  26 |
1756:  27 | 		// Wait for merchant data to load completely
1757:  > 28 | 		await expect(page.getByRole('heading', { name: 'Green Town', exact: true })).toBeVisible();
1758:  | 		                                                                             ^
1759:  29 | 		await expect(page.getByText('Last Surveyed')).toBeVisible();
1760:  30 |
1761:  31 | 		// Wait for boost button to be interactive
1762:  at /home/runner/work/btcmap.org/btcmap.org/tests/boost-invoice.spec.ts:28:80
1763:  Error Context: test-results/boost-invoice-Boost-Invoic-f5e10-ce-through-complete-UI-flow-chromium-retry1/error-context.md
1764:  attachment #2: trace (application/zip) ─────────────────────────────────────────────────────────
1765:  test-results/boost-invoice-Boost-Invoic-f5e10-ce-through-complete-UI-flow-chromium-retry1/trace.zip
1766:  Usage:
1767:  yarn playwright show-trace test-results/boost-invoice-Boost-Invoic-f5e10-ce-through-complete-UI-flow-chromium-retry1/trace.zip
1768:  ────────────────────────────────────────────────────────────────────────────────────────────────
1769:  Retry #2 ───────────────────────────────────────────────────────────────────────────────────────
1770:  Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoBeVisible�[2m(�[22m�[2m)�[22m failed
1771:  Locator: getByRole('heading', { name: 'Green Town', exact: true })
1772:  Expected: visible
1773:  Timeout: 5000ms
1774:  Error: element(s) not found
1775:  Call log:
1776:  �[2m  - Expect "toBeVisible" with timeout 5000ms�[22m
1777:  �[2m  - waiting for getByRole('heading', { name: 'Green Town', exact: true })�[22m
1778:  26 |
1779:  27 | 		// Wait for merchant data to load completely
1780:  > 28 | 		await expect(page.getByRole('heading', { name: 'Green Town', exact: true })).toBeVisible();
1781:  | 		                                                                             ^
1782:  29 | 		await expect(page.getByText('Last Surveyed')).toBeVisible();
1783:  30 |
1784:  31 | 		// Wait for boost button to be interactive
1785:  at /home/runner/work/btcmap.org/btcmap.org/tests/boost-invoice.spec.ts:28:80
1786:  Error Context: test-results/boost-invoice-Boost-Invoic-f5e10-ce-through-complete-UI-flow-chromium-retry2/error-context.md
1787:  2) [chromium] › tests/deleted-merchant.spec.ts:20:2 › Deleted Merchant Page › renders deleted merchant detail view instead of 500 
1788:  Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBe�[2m(�[22m�[32mexpected�[39m�[2m) // Object.is equality�[22m
1789:  Expected: �[32m200�[39m
1790:  Received: �[31m500�[39m
1791:  22 |
1792:  23 | 		// Must not be a server error
1793:  > 24 | 		expect(response?.status()).toBe(200);
1794:  | 		                           ^
1795:  25 | 	});
1796:  26 |
1797:  27 | 	test('shows deleted notice banner', async ({ page }) => {
1798:  at /home/runner/work/btcmap.org/btcmap.org/tests/deleted-merchant.spec.ts:24:30
1799:  Error Context: test-results/deleted-merchant-Deleted-M-200a4--detail-view-instead-of-500-chromium/error-context.md
1800:  Retry #1 ───────────────────────────────────────────────────────────────────────────────────────
1801:  Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBe�[2m(�[22m�[32mexpected�[39m�[2m) // Object.is equality�[22m
1802:  Expected: �[32m200�[39m
1803:  Received: �[31m500�[39m
1804:  22 |
1805:  23 | 		// Must not be a server error
1806:  > 24 | 		expect(response?.status()).toBe(200);
1807:  | 		                           ^
1808:  25 | 	});
1809:  26 |
1810:  27 | 	test('shows deleted notice banner', async ({ page }) => {
1811:  at /home/runner/work/btcmap.org/btcmap.org/tests/deleted-merchant.spec.ts:24:30
1812:  Error Context: test-results/deleted-merchant-Deleted-M-200a4--detail-view-instead-of-500-chromium-retry1/error-context.md
1813:  attachment #2: trace (application/zip) ─────────────────────────────────────────────────────────
1814:  test-results/deleted-merchant-Deleted-M-200a4--detail-view-instead-of-500-chromium-retry1/trace.zip
1815:  Usage:
1816:  yarn playwright show-trace test-results/deleted-merchant-Deleted-M-200a4--detail-view-instead-of-500-chromium-retry1/trace.zip
1817:  ────────────────────────────────────────────────────────────────────────────────────────────────
1818:  Retry #2 ───────────────────────────────────────────────────────────────────────────────────────
1819:  Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBe�[2m(�[22m�[32mexpected�[39m�[2m) // Object.is equality�[22m
1820:  Expected: �[32m200�[39m
1821:  Received: �[31m500�[39m
1822:  22 |
1823:  23 | 		// Must not be a server error
1824:  > 24 | 		expect(response?.status()).toBe(200);
1825:  | 		                           ^
1826:  25 | 	});
1827:  26 |
1828:  27 | 	test('shows deleted notice banner', async ({ page }) => {
1829:  at /home/runner/work/btcmap.org/btcmap.org/tests/deleted-merchant.spec.ts:24:30
1830:  Error Context: test-results/deleted-merchant-Deleted-M-200a4--detail-view-instead-of-500-chromium-retry2/error-context.md
1831:  3) [chromium] › tests/deleted-merchant.spec.ts:27:2 › Deleted Merchant Page › shows deleted notice banner 
1832:  Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoBeVisible�[2m(�[22m�[2m)�[22m failed
1833:  Locator: getByText('This merchant has been removed from BTC Map')
1834:  Expected: visible
1835:  Timeout: 5000ms
1836:  Error: element(s) not found
1837:  Call log:
1838:  �[2m  - Expect "toBeVisible" with timeout 5000ms�[22m
1839:  �[2m  - waiting for getByText('This merchant has been removed from BTC Map')�[22m
1840:  30 | 		await expect(
1841:  31 | 			page.getByText('This merchant has been removed from BTC Map')
1842:  > 32 | 		).toBeVisible();
1843:  | 		  ^
1844:  33 |
1845:  34 | 		await expect(
1846:  35 | 			page.getByText('outdated and for reference only')
1847:  at /home/runner/work/btcmap.org/btcmap.org/tests/deleted-merchant.spec.ts:32:5
1848:  Error Context: test-results/deleted-merchant-Deleted-M-a59c5-shows-deleted-notice-banner-chromium/error-context.md
1849:  Retry #1 ───────────────────────────────────────────────────────────────────────────────────────
1850:  Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoBeVisible�[2m(�[22m�[2m)�[22m failed
1851:  Locator: getByText('This merchant has been removed from BTC Map')
1852:  Expected: visible
1853:  Timeout: 5000ms
1854:  Error: element(s) not found
1855:  Call log:
1856:  �[2m  - Expect "toBeVisible" with timeout 5000ms�[22m
1857:  �[2m  - waiting for getByText('This merchant has been removed from BTC Map')�[22m
1858:  30 | 		await expect(
1859:  31 | 			page.getByText('This merchant has been removed from BTC Map')
1860:  > 32 | 		).toBeVisible();
1861:  | 		  ^
1862:  33 |
1863:  34 | 		await expect(
1864:  35 | 			page.getByText('outdated and for reference only')
1865:  at /home/runner/work/btcmap.org/btcmap.org/tests/deleted-merchant.spec.ts:32:5
1866:  Error Context: test-results/deleted-merchant-Deleted-M-a59c5-shows-deleted-notice-banner-chromium-retry1/error-context.md
1867:  attachment #2: trace (application/zip) ─────────────────────────────────────────────────────────
1868:  test-results/deleted-merchant-Deleted-M-a59c5-shows-deleted-notice-banner-chromium-retry1/trace.zip
1869:  Usage:
1870:  yarn playwright show-trace test-results/deleted-merchant-Deleted-M-a59c5-shows-deleted-notice-banner-chromium-retry1/trace.zip
1871:  ────────────────────────────────────────────────────────────────────────────────────────────────
1872:  Retry #2 ───────────────────────────────────────────────────────────────────────────────────────
1873:  Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoBeVisible�[2m(�[22m�[2m)�[22m failed
1874:  Locator: getByText('This merchant has been removed from BTC Map')
1875:  Expected: visible
1876:  Timeout: 5000ms
1877:  Error: element(s) not found
1878:  Call log:
1879:  �[2m  - Expect "toBeVisible" with timeout 5000ms�[22m
1880:  �[2m  - waiting for getByText('This merchant has been removed from BTC Map')�[22m
1881:  30 | 		await expect(
1882:  31 | 			page.getByText('This merchant has been removed from BTC Map')
1883:  > 32 | 		).toBeVisible();
1884:  | 		  ^
1885:  33 |
1886:  34 | 		await expect(
1887:  35 | 			page.getByText('outdated and for reference only')
1888:  at /home/runner/work/btcmap.org/btcmap.org/tests/deleted-merchant.spec.ts:32:5
1889:  Error Context: test-results/deleted-merchant-Deleted-M-a59c5-shows-deleted-notice-banner-chromium-retry2/error-context.md
1890:  4) [chromium] › tests/deleted-merchant.spec.ts:39:2 › Deleted Merchant Page › displays merchant name with Deleted suffix 
1891:  Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoBeVisible�[2m(�[22m�[2m)�[22m failed
1892:  Locator: getByRole('heading', { name: /Tabaccheria Il Quadrifoglio.*Deleted/i })
1893:  Expected: visible
1894:  Timeout: 5000ms
1895:  Error: element(s) not found
1896:  Call log:
1897:  �[2m  - Expect "toBeVisible" with timeout 5000ms�[22m
1898:  �[2m  - waiting for getByRole('heading', { name: /Tabaccheria Il Quadrifoglio.*Deleted/i })�[22m
1899:  42 | 		await expect(
1900:  43 | 			page.getByRole('heading', { name: /Tabaccheria Il Quadrifoglio.*Deleted/i })
1901:  > 44 | 		).toBeVisible();
1902:  | 		  ^
1903:  45 | 	});
1904:  46 |
1905:  47 | 	test('renders map for deleted merchant', async ({ page }) => {
1906:  at /home/runner/work/btcmap.org/btcmap.org/tests/deleted-merchant.spec.ts:44:5
1907:  Error Context: test-results/deleted-merchant-Deleted-M-3e0e0-nt-name-with-Deleted-suffix-chromium/error-context.md
1908:  Retry #1 ───────────────────────────────────────────────────────────────────────────────────────
1909:  Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoBeVisible�[2m(�[22m�[2m)�[22m failed
1910:  Locator: getByRole('heading', { name: /Tabaccheria Il Quadrifoglio.*Deleted/i })
1911:  Expected: visible
1912:  Timeout: 5000ms
1913:  Error: element(s) not found
1914:  Call log:
1915:  �[2m  - Expect "toBeVisible" with timeout 5000ms�[22m
1916:  �[2m  - waiting for getByRole('heading', { name: /Tabaccheria Il Quadrifoglio.*Deleted/i })�[22m
1917:  42 | 		await expect(
1918:  43 | 			page.getByRole('heading', { name: /Tabaccheria Il Quadrifoglio.*Deleted/i })
1919:  > 44 | 		).toBeVisible();
1920:  | 		  ^
1921:  45 | 	});
1922:  46 |
1923:  47 | 	test('renders map for deleted merchant', async ({ page }) => {
1924:  at /home/runner/work/btcmap.org/btcmap.org/tests/deleted-merchant.spec.ts:44:5
1925:  Error Context: test-results/deleted-merchant-Deleted-M-3e0e0-nt-name-with-Deleted-suffix-chromium-retry1/error-context.md
1926:  attachment #2: trace (application/zip) ─────────────────────────────────────────────────────────
1927:  test-results/deleted-merchant-Deleted-M-3e0e0-nt-name-with-Deleted-suffix-chromium-retry1/trace.zip
1928:  Usage:
1929:  yarn playwright show-trace test-results/deleted-merchant-Deleted-M-3e0e0-nt-name-with-Deleted-suffix-chromium-retry1/trace.zip
1930:  ────────────────────────────────────────────────────────────────────────────────────────────────
1931:  Retry #2 ───────────────────────────────────────────────────────────────────────────────────────
1932:  Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoBeVisible�[2m(�[22m�[2m)�[22m failed
1933:  Locator: getByRole('heading', { name: /Tabaccheria Il Quadrifoglio.*Deleted/i })
1934:  Expected: visible
1935:  Timeout: 5000ms
1936:  Error: element(s) not found
1937:  Call log:
1938:  �[2m  - Expect "toBeVisible" with timeout 5000ms�[22m
1939:  �[2m  - waiting for getByRole('heading', { name: /Tabaccheria Il Quadrifoglio.*Deleted/i })�[22m
1940:  42 | 		await expect(
1941:  43 | 			page.getByRole('heading', { name: /Tabaccheria Il Quadrifoglio.*Deleted/i })
1942:  > 44 | 		).toBeVisible();
1943:  | 		  ^
1944:  45 | 	});
1945:  46 |
1946:  47 | 	test('renders map for deleted merchant', async ({ page }) => {
1947:  at /home/runner/work/btcmap.org/btcmap.org/tests/deleted-merchant.spec.ts:44:5
1948:  Error Context: test-results/deleted-merchant-Deleted-M-3e0e0-nt-name-with-Deleted-suffix-chromium-retry2/error-context.md
1949:  5) [chromium] › tests/deleted-merchant.spec.ts:47:2 › Deleted Merchant Page › renders map for deleted merchant 
1950:  Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoBeVisible�[2m(�[22m�[2m)�[22m failed
1951:  Locator: locator('.leaflet-container')
1952:  Expected: visible
1953:  Timeout: 15000ms
1954:  Error: element(s) not found
1955:  Call log:
1956:  �[2m  - Expect "toBeVisible" with timeout 15000ms�[22m
1957:  �[2m  - waiting for locator('.leaflet-container')�[22m
1958:  49 |
1959:  50 | 		// Map container should still render for deleted merchants
1960:  > 51 | 		await expect(page.locator('.leaflet-container')).toBeVisible({ timeout: 15000 });
1961:  | 		                                                 ^
1962:  52 | 	});
1963:  53 | });
1964:  54 |
1965:  at /home/runner/work/btcmap.org/btcmap.org/tests/deleted-merchant.spec.ts:51:52
1966:  Error Context: test-results/deleted-merchant-Deleted-M-34103-rs-map-for-deleted-merchant-chromium/error-context.md
1967:  Retry #1 ───────────────────────────────────────────────────────────────────────────────────────
1968:  Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoBeVisible�[2m(�[22m�[2m)�[22m failed
1969:  Locator: locator('.leaflet-container')
1970:  Expected: visible
1971:  Timeout: 15000ms
1972:  Error: element(s) not found
1973:  Call log:
1974:  �[2m  - Expect "toBeVisible" with timeout 15000ms�[22m
1975:  �[2m  - waiting for locator('.leaflet-container')�[22m
1976:  49 |
1977:  50 | 		// Map container should still render for deleted merchants
1978:  > 51 | 		await expect(page.locator('.leaflet-container')).toBeVisible({ timeout: 15000 });
1979:  | 		                                                 ^
1980:  52 | 	});
1981:  53 | });
1982:  54 |
1983:  at /home/runner/work/btcmap.org/btcmap.org/tests/deleted-merchant.spec.ts:51:52
1984:  Error Context: test-results/deleted-merchant-Deleted-M-34103-rs-map-for-deleted-merchant-chromium-retry1/error-context.md
1985:  attachment #2: trace (application/zip) ─────────────────────────────────────────────────────────
1986:  test-results/deleted-merchant-Deleted-M-34103-rs-map-for-deleted-merchant-chromium-retry1/trace.zip
1987:  Usage:
1988:  yarn playwright show-trace test-results/deleted-merchant-Deleted-M-34103-rs-map-for-deleted-merchant-chromium-retry1/trace.zip
1989:  ────────────────────────────────────────────────────────────────────────────────────────────────
1990:  Retry #2 ───────────────────────────────────────────────────────────────────────────────────────
1991:  Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoBeVisible�[2m(�[22m�[2m)�[22m failed
1992:  Locator: locator('.leaflet-container')
1993:  Expected: visible
1994:  Timeout: 15000ms
1995:  Error: element(s) not found
1996:  Call log:
1997:  �[2m  - Expect "toBeVisible" with timeout 15000ms�[22m
1998:  �[2m  - waiting for locator('.leaflet-container')�[22m
1999:  49 |
2000:  50 | 		// Map container should still render for deleted merchants
2001:  > 51 | 		await expect(page.locator('.leaflet-container')).toBeVisible({ timeout: 15000 });
2002:  | 		                                                 ^
2003:  52 | 	});
2004:  53 | });
2005:  54 |
2006:  at /home/runner/work/btcmap.org/btcmap.org/tests/deleted-merchant.spec.ts:51:52
2007:  Error Context: test-results/deleted-merchant-Deleted-M-34103-rs-map-for-deleted-merchant-chromium-retry2/error-context.md
2008:  5 failed
2009:  [chromium] › tests/boost-invoice.spec.ts:8:2 › Boost Invoice Generation › generates valid invoice through complete UI flow 
2010:  [chromium] › tests/deleted-merchant.spec.ts:20:2 › Deleted Merchant Page › renders deleted merchant detail view instead of 500 
2011:  [chromium] › tests/deleted-merchant.spec.ts:27:2 › Deleted Merchant Page › shows deleted notice banner 
2012:  [chromium] › tests/deleted-merchant.spec.ts:39:2 › Deleted Merchant Page › displays merchant name with Deleted suffix 
2013:  [chromium] › tests/deleted-merchant.spec.ts:47:2 › Deleted Merchant Page › renders map for deleted merchant 
2014:  37 passed (1.7m)
2015:  ##[error]Process completed with exit code 1.
2016:  ##[group]Run actions/upload-artifact@v7

Comment on lines +140 to +147
let localizedNameFetched = false;

$: {
const lang = navigator.language;
if (lang !== "en") {
fetchLocalizedName(data.id, data.name, lang);
}
localizedNameFetched = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

Action required

1. localizednamefetched unused variable 📘 Rule violation ✓ Correctness

The PR introduces localizedNameFetched but it is never read, which commonly triggers
no-unused-vars lint errors. This can cause yarn run lint to fail.
Agent Prompt
## Issue description
`localizedNameFetched` is declared and assigned but never read, which can cause lint failures (unused variable).

## Issue Context
The variable appears intended to prevent repeated fetches from the reactive block, but it currently does not affect control flow.

## Fix Focus Areas
- src/routes/merchant/[id]/+page.svelte[140-147]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +142 to +146
$: {
const lang = navigator.language;
if (lang !== "en") {
fetchLocalizedName(data.id, data.name, lang);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Action required

2. Ssr crash on navigator 🐞 Bug ⛯ Reliability

The new reactive block reads navigator.language unconditionally, which will throw during SSR and
break rendering of the merchant page. This component already uses browser guards elsewhere, so
this is a high-confidence regression.
Agent Prompt
## Issue description
Merchant page SSR can crash because a `$:` reactive block reads `navigator.language` unconditionally.

## Issue Context
SvelteKit renders this component during SSR. Browser-only APIs (`navigator`) must be guarded.

## Fix Focus Areas
- src/routes/merchant/[id]/+page.svelte[139-148]

### Suggested change
Wrap the reactive block with a `browser` check (already imported in this file), e.g.:
- `if (!browser) return;`
- then read `navigator.language` and trigger the fetch.

Alternatively: move the localized-name fetch into `onMount` so it never runs on the server.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +142 to +163
$: {
const lang = navigator.language;
if (lang !== "en") {
fetchLocalizedName(data.id, data.name, lang);
}
localizedNameFetched = true;
}

async function fetchLocalizedName(
placeId: string,
defaultName: string | undefined,
lang: string,
) {
try {
const response = await fetch(
`https://api.btcmap.org/v4/places/${placeId}?fields=name&lang=${lang}`,
);
if (!response.ok) return;
const data = await response.json();
if (data.name && data.name !== defaultName) {
localizedName = data.name;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Action required

3. Racy/stale localized name 🐞 Bug ✓ Correctness

Client-side navigation between merchant IDs can briefly show the previous merchant’s localized name,
and a slower previous fetch can overwrite the current merchant name due to missing reset and missing
request-generation guard. This affects title/H1 and can confuse users.
Agent Prompt
## Issue description
`localizedName` can become stale and async results can race when navigating between different merchant IDs in the same route component instance.

## Issue Context
SvelteKit can reuse `+page.svelte` instances across parameter changes. Without resetting state and guarding async updates, an older fetch can overwrite newer data.

## Fix Focus Areas
- src/routes/merchant/[id]/+page.svelte[139-167]

### Suggested approach
- Introduce a request generation counter (similar to `CountryCard.svelte`).
- In the reactive block triggered by `data.id` / `data.name`, set `localizedName = undefined` (or `localizedName = data.name`) before kicking off the fetch.
- When the fetch resolves, only apply the result if the generation id matches the latest one (or `placeId === data.id` at time of resolution).
- Optionally use `AbortController` to cancel in-flight requests when `data.id` changes.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/routes/merchant/`[id]/+page.svelte:
- Around line 142-167: The reactive block that uses navigator.language and
fetchLocalizedName is not SSR-safe and can produce stale localizedName on
navigation; wrap the reactive block in a browser guard (import { browser } from
"$app/environment") and use the $locale store as the primary locale source
before falling back to navigator.language, reset localizedName when data.id
changes, and implement per-request cancellation by tracking a local requestId
(increment on each fetch) inside fetchLocalizedName so responses with
non-matching requestId are ignored; update the $: reactive block to call
fetchLocalizedName(data.id, data.name, locale, currentRequestId) only in browser
and after resetting localizedName, and modify fetchLocalizedName to
accept/validate the requestId and early-return if it’s stale.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: aa9a7c56-0d77-4798-b2fc-5108f9702b74

📥 Commits

Reviewing files that changed from the base of the PR and between 949e73a and a141c33.

📒 Files selected for processing (1)
  • src/routes/merchant/[id]/+page.svelte

Comment on lines +142 to +167
$: {
const lang = navigator.language;
if (lang !== "en") {
fetchLocalizedName(data.id, data.name, lang);
}
localizedNameFetched = true;
}

async function fetchLocalizedName(
placeId: string,
defaultName: string | undefined,
lang: string,
) {
try {
const response = await fetch(
`https://api.btcmap.org/v4/places/${placeId}?fields=name&lang=${lang}`,
);
if (!response.ok) return;
const data = await response.json();
if (data.name && data.name !== defaultName) {
localizedName = data.name;
}
} catch {
// Silent failure - page continues with original name
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

SvelteKit Svelte reactive statements $: run during server-side rendering SSR browser globals navigator unavailable

💡 Result:

In SvelteKit, your component’s <script> (including legacy reactive statements like $:) is executed during server-side rendering (SSR) to produce the initial HTML. Since SSR runs in a server (Node/edge) environment, browser globals like navigator, window, and document don’t exist there—so $: navigator... will throw. SvelteKit’s docs explicitly note that SSR means the page is rendered on the server first, then hydrated in the browser later [1].

Fix patterns

  1. Guard browser-only code
<script>
  import { browser } from '$app/environment';

  $: if (browser) {
    console.log(navigator.userAgent);
  }
</script>

browser is true only in the browser, false on the server [2].

  1. Move browser-only work to onMount (Svelte 3/4/5)
<script>
  import { onMount } from 'svelte';

  onMount(() => {
    console.log(navigator.userAgent);
  });
</script>

onMount does not run during server-side rendering [3].

  1. (Svelte 5 runes) Use $effect for client-only effects
    $effect also does not run during SSR (so it’s safe for navigator/DOM usage) [3].

  2. Disable SSR for a page/layout (last resort)

// +page.js (or +layout.js)
export const ssr = false;

This renders an empty shell on the server and does everything on the client [1].


Sources

[1] SvelteKit Docs — Page options (ssr) https://svelte.dev/docs/kit/page-options
[2] SvelteKit Docs — $app/environment (browser) https://svelte.dev/docs/kit/%24app-environment
[3] Svelte Docs — onMount / $effect SSR behavior https://svelte.dev/docs/svelte/svelte
[4] Stack Overflow — reactive blocks don’t wait for mount (run during init) https://stackoverflow.com/questions/64639780/how-do-you-write-a-reactive-statement-in-svelte-sapper-when-it-needs-to-access-a


🏁 Script executed:

# Check the actual code at Lines 142-167 in the merchant page
head -n 200 src/routes/merchant/[id]/+page.svelte | tail -n 70

Repository: teambtcmap/btcmap.org

Length of output: 2061


🏁 Script executed:

# Check the imports at the top of the file to see if `browser` is imported
head -n 60 src/routes/merchant/[id]/+page.svelte

Repository: teambtcmap/btcmap.org

Length of output: 1964


🏁 Script executed:

# Verify the i18n setup and locale store
cat -n src/lib/i18n/index.ts | head -n 80

Repository: teambtcmap/btcmap.org

Length of output: 1642


Make the localized-name fetch SSR-safe and navigation-safe.

The reactive block at line 142 reads navigator.language without a browser guard, causing SSR to fail since browser globals don't exist on the server. Additionally, the block never clears localizedName or rejects stale responses, so client-side navigation between merchants can momentarily show the previous merchant's name or allow an older request to overwrite the new page data. The new <svelte:head> values will also remain untranslated in the SSR HTML because the fetch only runs in the browser.

Wrap the reactive block with browser, use the $locale store as the first locale source, reset localizedName when data.id changes, and track request IDs to ignore outdated responses.

🛠️ Fix sketch
-import { _ } from "$lib/i18n";
+import { _, locale } from "$lib/i18n";
...
 let localizedName: string | undefined;
-let localizedNameFetched = false;
+let localizedNameRequest = 0;

-$: {
-	const lang = navigator.language;
-	if (lang !== "en") {
-		fetchLocalizedName(data.id, data.name, lang);
-	}
-	localizedNameFetched = true;
-}
+$: if (browser) {
+	const lang = $locale ?? navigator.languages?.[0] ?? navigator.language ?? "en";
+	const requestId = ++localizedNameRequest;
+	localizedName = undefined;
+
+	if (!lang.startsWith("en")) {
+		void fetchLocalizedName(data.id, data.name, lang, requestId);
+	}
+}

 async function fetchLocalizedName(
 	placeId: string,
 	defaultName: string | undefined,
 	lang: string,
+	requestId: number,
 ) {
 	try {
 		const response = await fetch(
 			`https://api.btcmap.org/v4/places/${placeId}?fields=name&lang=${lang}`,
 		);
 		if (!response.ok) return;
 		const data = await response.json();
-		if (data.name && data.name !== defaultName) {
+		if (
+			requestId === localizedNameRequest &&
+			data.name &&
+			data.name !== defaultName
+		) {
 			localizedName = data.name;
 		}
 	} catch {
 		// Silent failure - page continues with original name
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/merchant/`[id]/+page.svelte around lines 142 - 167, The reactive
block that uses navigator.language and fetchLocalizedName is not SSR-safe and
can produce stale localizedName on navigation; wrap the reactive block in a
browser guard (import { browser } from "$app/environment") and use the $locale
store as the primary locale source before falling back to navigator.language,
reset localizedName when data.id changes, and implement per-request cancellation
by tracking a local requestId (increment on each fetch) inside
fetchLocalizedName so responses with non-matching requestId are ignored; update
the $: reactive block to call fetchLocalizedName(data.id, data.name, locale,
currentRequestId) only in browser and after resetting localizedName, and modify
fetchLocalizedName to accept/validate the requestId and early-return if it’s
stale.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant