feat(public): type the brand block so mobile clients get typed theming
All checks were successful
PR Checks / client-build (pull_request) Successful in 9m29s
PR Checks / server-tests (pull_request) Successful in 10m30s
PR Checks / bot-install (pull_request) Successful in 9m21s

Branding is already returned by GET /public/settings (the `brand` block:
name/colors/logo/hero/favicon, per-shard from BRAND_*). §8.6 of the Android
plan asks to confirm it — this makes it a first-class part of the contract so
the app's OpenAPI codegen produces typed branding instead of an untyped map.

- Swagger: add Brand + PublicSettings schemas; /public/settings now references
  PublicSettings (was additionalProperties:true). Brand documents that asset
  fields may be site-relative paths (resolve against the base URL).
- test/publicBrand.test.js locks the brand theming contract the app depends on
  (all fields present; BRAND_* defaults; admin site_title/contact_email
  overrides; accentInt never leaked).

No behavior change to the response — it already carried `brand`; this types and
guards it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr
This commit is contained in:
2026-07-19 11:58:01 -05:00
parent 90c8eae20f
commit c35509e8b3
4 changed files with 367 additions and 8 deletions

View File

@@ -587,6 +587,40 @@ const doc = {
server: { type: 'string', example: '1.0.0', description: 'Server package version (informational).' },
},
},
Brand: {
type: 'object',
description:
'Per-shard branding (BRAND_* env, with admin overrides for name/contactEmail). A client themes itself from this — one instance runs as any shard. Asset fields (logo/hero/favicon) may be site-relative paths; resolve them against the site base URL.',
properties: {
name: { type: 'string', example: 'Runic Gateway' },
shortName: { type: 'string', example: 'Runic Gateway' },
tagline: { type: 'string', example: 'an independent private Ultima Online shard' },
description: { type: 'string' },
contactEmail: { type: 'string', example: '' },
url: { type: 'string', example: '' },
accent: { type: 'string', example: '#7f99bd', description: 'Seed/accent color (hex) for theming.' },
logo: { type: 'string', example: '', description: 'Logo URL or site-relative path; empty = no logo.' },
hero: { type: 'string', example: '/assets/img/runic-emblem.png', description: 'Hero image URL or site-relative path.' },
favicon: { type: 'string', example: '/assets/img/favicon.ico', description: 'Favicon URL or site-relative path.' },
},
},
PublicSettings: {
type: 'object',
description:
'Public site settings + branding (GET /public/settings). Whitelisted string settings, plus derived availability flags and the brand block a client themes from. Additional whitelisted keys may appear.',
properties: {
site_title: { type: 'string', example: 'Runic Gateway' },
status_message: { type: 'string', example: '' },
maintenance_message: { type: 'string', example: '' },
registration: {
type: 'object',
properties: { password: { type: 'boolean' }, sso: { type: 'boolean' } },
},
gameAccountSignup: { type: 'boolean', example: false },
brand: { $ref: '#/components/schemas/Brand' },
},
additionalProperties: true,
},
// Delete/mutation acknowledgements — each echoes the affected resource key
// or a boolean flag rather than a { message } string.
DeletedId: {