fix(shard): answer with the instance name when the shard is unnamed

ServUO ships Server.cfg with `Name=My Shard`. An operator who never edited it
publishes that verbatim, so the rules page read "My Shard" under a header
carrying the real name. That value is the shard saying *unnamed* rather than
naming anything, so the site now answers with its own.

`settings.getInstanceName()` resolves `site_title || BRAND_NAME` — the same
resolution `getPublic().brand.name` already uses, so an install that set only
the site title can never show two different names on two pages. Bare
`brand.name` would have been wrong for exactly that case.

Substituted at INGEST rather than on read: world.ruleset is also broadcast
live, and the same object is handed to the SSE fan-out, so a read-time fix
would be undone by the next reconnect's frame. Matched case- and
padding-insensitively but only as a whole value, so a shard genuinely called
"My Shard Reborn" keeps its name.

Fixes a second ruleset writer found on the way: uoLinkSocket.backfill() called
shardState.setRuleset directly instead of going through the dispatcher as
ingestEach does, so the boot/reconnect snapshot silently skipped this
normalization. The two arrival orders have to produce the same stored frame.

Also renders a placeholder row on an unscored leaderboard — the instance name
with an em dash where a score goes, deliberately not shaped like an entry (no
medal, no bar) because a placeholder that looked like a real standing would be
a fabricated one. Presentation only; the API still sends an empty `top`.

Verified live against the shard + sidecar: rules page and leaderboards on web
and Android both correct.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U7CBg11prhLimL9iHSX1bP
This commit is contained in:
2026-08-01 00:58:21 -05:00
parent e50fab241f
commit 01a559792c
5 changed files with 149 additions and 4 deletions

View File

@@ -70,6 +70,24 @@ async function isMobileAppLinksEnabled() {
}
}
/**
* This instance's name, resolved exactly as `getPublic().brand.name` resolves it —
* the admin-editable site title wins over BRAND_NAME. Anything that has to *speak*
* the instance's name outside the settings payload must use this rather than
* `brand.name`, or an install that set only the site title gets two different names
* on two different pages.
*
* Never throws: a name is always better than an error, so a DB fault falls back to
* the env value.
*/
async function getInstanceName() {
try {
return (await settingsDb.get('site_title')) || brand.name
} catch {
return brand.name
}
}
async function get(key) {
return settingsDb.get(key)
}
@@ -154,6 +172,7 @@ module.exports = {
setMany,
getAll,
getPublic,
getInstanceName,
PUBLIC_KEYS,
REGISTRATION_KEY,
REGISTRATION_MODES,