From a97a1e9c4134df2ad1ca81c611f17735ebb7afcf Mon Sep 17 00:00:00 2001 From: CaYatur Date: Wed, 29 Jul 2026 22:11:38 +0300 Subject: [PATCH] The map page was missing half its host contract (#153) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ReferenceError: MAP_ICONS is not defined at mapDraw The map page is the FOURTH host of the shared map engine and was given only part of what that engine asks for. `STRUCTURE_ICONS`, `MAP_ICONS`, `mapIconFor` and `mapIconSvg` are all supplied by the panel and by the public site, and by nothing on this page. With structures switched on, `mapDraw` threw and took the rest of the draw down with it — so the map stopped updating entirely, on the page whose only purpose is the map. It shipped because the smoke ran the panel's script and the site's and never this one. It does now, with every layer ON — the defect only appeared with structures enabled, so a page tested at its defaults would still have passed. Removing `MAP_ICONS` again fails with "the map page never defines MAP_ICONS", and the test then draws, which is what threw. Bottom-left is a STACK now, not three things at the same coordinates. The legend, the structure key and the mouse coordinates were all pinned to the corner and drew over each other, which is why the coordinates were unreadable. Each sits above the one below it, at offsets measured from their real heights — every one of them wraps at some width, so a constant is wrong somewhere. Re-measured after each draw, because the structure key comes and goes with its toggle. The page also takes the operator's own website colours instead of a hardcoded palette. Somebody who chose their site's accent was getting a map page in a different red, which reads as a different product. And the desktop panel lists API keys again. `refresh()` was one sequential chain with the keys awaited LAST, called everywhere as `void refresh()` — so anything that threw earlier left the list empty and said nothing. "I created a key and it is not listed" was not a problem with keys; it was a problem with being fourth in a queue that could stop. Four independent loads, `allSettled`, and a failure is reported rather than swallowed. Verified: 12/12 gates, and the generator checked directly rather than through a running app — rendering `getMapPageHtml` in isolation confirms the theme reaches the CSS, `MAP_ICONS` is defined, and the stack offsets are there. --- src/main/smoke.ts | 44 +++++++++++++++ src/main/web/mapPageHtml.ts | 74 +++++++++++++++++++++---- src/main/web/server.ts | 4 +- src/renderer/src/views/WebPanelView.tsx | 45 +++++++++++---- 4 files changed, 143 insertions(+), 24 deletions(-) diff --git a/src/main/smoke.ts b/src/main/smoke.ts index d514cfa..9a918ef 100644 --- a/src/main/smoke.ts +++ b/src/main/smoke.ts @@ -132,6 +132,7 @@ import * as areasMod2 from './core/chunkAreas' import * as tilesMod from './core/worldTiles' import * as tex from '@shared/textures' import { MAP_CSS, MAP_HTML } from '@shared/mapUi' +import { getMapPageHtml } from './web/mapPageHtml' import * as pngMod from './core/png' import { deflateSync } from 'node:zlib' import * as assetsMod from './core/clientAssets' @@ -6782,6 +6783,49 @@ export async function runWebSmoke(): Promise { current: { id, name: 'S', scopes: ['view', 'store'], status: 'stopped' } }) site = runPageScript(getPublicSiteHtml()) + + // The map page is the FOURTH host of the shared map engine, and until + // #153 the smoke never ran its script — so it shipped missing half the + // host contract. With structures switched on, `mapDraw` threw + // "MAP_ICONS is not defined" and took the whole draw down with it: the + // map simply stopped updating, on the page whose entire purpose is the + // map. + // + // Every setting ON, because the defect only appeared with structures + // enabled and a page tested at its defaults would still pass. + const mapPage = runPageScript( + getMapPageHtml( + normalizeMapPage({ + enabled: true, + serverId: id, + structures: true, + areas: true, + heatmap: true, + world: true, + players: true, + names: true, + heads: true + }) + ) + ) + const mctx = mapPage.ctx as Record + // What MAP_JS reaches for and this page has to supply. Naming them is + // the point: a missing one is a ReferenceError at draw time, which is + // silent until somebody opens the page with that layer on. + for (const need of [ + 'mapGet', 'mapPost', 'mapServerId', 'mapFeedUrl', 'mapTilesUrl', + 'mapAvatarUrl', 'mapIconFor', 'mapIconSvg', 'MAP_ICONS', 'STRUCTURE_ICONS' + ]) { + if (mctx[need] === undefined) return fail('the map page never defines ' + need) + } + // ...and then actually draw, which is what threw. Structures on, so the + // icon key is built rather than skipped. + try { + ;(mctx['MAP'] as { marksOn: boolean; areasOn: boolean }).marksOn = true + ;(mctx['mapDraw'] as () => void)() + } catch (e) { + return fail('the map page threw while drawing: ' + String(e)) + } } catch (e) { return fail('a served page threw on load: ' + String(e)) } diff --git a/src/main/web/mapPageHtml.ts b/src/main/web/mapPageHtml.ts index 6dcaf78..ff62841 100644 --- a/src/main/web/mapPageHtml.ts +++ b/src/main/web/mapPageHtml.ts @@ -2,6 +2,8 @@ import { MAP_CSS, MAP_HTML, MAP_JS } from '@shared/mapUi' import { mapPagePublic } from '@shared/mapPage' import type { MapPageConfig } from '@shared/mapPage' import { avatarUrl } from '@shared/profile' +import { iconSvg, STRUCTURE_ICONS } from '@shared/mapIcons' +import type { SiteTheme } from '@shared/web' /** * The map page (#146). @@ -21,8 +23,18 @@ const esc = (s: string): string => c === '&' ? '&' : c === '<' ? '<' : c === '>' ? '>' : c === '"' ? '"' : ''' ) -export function getMapPageHtml(cfg: MapPageConfig): string { +export function getMapPageHtml(cfg: MapPageConfig, theme?: SiteTheme): string { const pub = mapPagePublic(cfg) + // The operator already chose colours for their website; a map page in a + // different red would look like somebody else's site. Falls back to the same + // defaults the site uses when nothing is configured. + const t: Partial = theme ?? {} + const hex = (v: unknown, d: string): string => + typeof v === 'string' && /^#[0-9a-fA-F]{6}$/.test(v) ? v : d + const accent = hex(t.accent, '#dc2727') + const bg = hex(t.bg, '#0b0b10') + const card = hex(t.card, '#16151b') + const text = hex(t.text, '#e7e9ee') return ` @@ -32,10 +44,15 @@ export function getMapPageHtml(cfg: MapPageConfig): string { ${esc(pub.title)}