`src/lib/furni/classname.ts` is imported by client components for its pure
string helpers, but it read `process.env.FURNI_BUNDLE_EXT` at module load.
In any client bundle without a `process` shim — including the UI fixture's
esbuild build — that threw `ReferenceError: process is not defined`, so the
lazily-loaded organize-imports dialog never mounted and `pnpm test:ui`
failed (studio.spec.ts). Fall back to the default extension when `process`
is absent; server behaviour is unchanged.
The deploy gate ran e2e specs that depend on live content and data, so a missing
upload (the /help article images) or a legacy malformed avatar figure could turn
a safe code deploy red. Split the suites by what they actually guarantee:
- Blocking gate (`pnpm test:e2e`, `playwright.config.ts`): only checks the CMS
can guarantee itself — anonymous redirects, register form fields, the captcha
DOM contract and the cache-control headers (deterministic origin-direct).
- Advisory suite (`pnpm test:e2e:quality`, `playwright.quality.config.ts`):
broken images, layout overflow and warm-page latency, which depend on live
content/upload state and the host. It runs as a `continue-on-error` CI job
against the public site so a real content problem stays visible without
blocking the pipeline.
Also add `scripts/sync-readme-versions.mjs` (+ tests, docs and a CI check) so
the pinned versions in README.md can never drift from package.json / .nvmrc.
The starter look for a new account was `hr-100-.hd-180-1...`, whose empty
hair colour slot is rejected by the imager with 400 "Invalid figure format".
Every player who registered without a look therefore got a broken avatar.
Add the missing colour slot (`hr-100-1`), which the imager renders.
Match the versions table and core-stack line to package.json after the
dependency bump: pnpm 12.11.2, Next.js 16.4.1, React 19.3.1,
Drizzle ORM 0.45.4, Zod 4.6.7, Vitest 5.0.4, Playwright 1.64.1.
Explain where the captcha is configured, how the admin panel and env
vars resolve (with a precedence table), step-by-step Turnstile setup,
that the site key is public while the secret must never be committed,
and how to verify. Also link it from Optional Integrations.
Document that CAPTCHA_PROVIDER accepts a provider name (e.g. turnstile)
and spell out that the site key is public while the secret must stay on
the host / in CI and never be committed.
- catalog: apply bulk offer edits in a single transaction with one RCON
refresh instead of N round-trips, virtualize the items table past 150
rows and cap the shop preview at 200 tiles
- cache: lift the public cache TTLs and the boot warm-up into env-tunable
constants (src/lib/perf-ttls.ts; CMS_*_TTL_SEC, CMS_WARMUP_ENABLED)
- auth: resolve the captcha provider, site key and secret from
CAPTCHA_PROVIDER, TURNSTILE_SITE_KEY and TURNSTILE_SECRET_KEY when the
admin settings are unset; an explicit DB value (including "none") still
wins, so Turnstile can be enabled per-deploy without touching the DB.
Commit 3c8abb6f made docker-update.sh exit 0 (SKIP) on a host owned by CI so
the cron does not report a failed deployment. The tests still expected a
nonzero exit for the ci-active/ci-green/ci-upstream scenarios, so five cases
failed.
- docker-update.test.ts: move the CI scenarios out of the non-zero group and
assert the skip: exit 0, 'This host is managed by CI' in the output and no
'compose up'. All three CI scenarios (both slot containers and the nginx
upstream) now share one test.
- README.md: document the exit-0 skip.
Bundle the remaining imaging hardening and the host/CI fixes from the
"Doe alles" pass into one release.
Furniture:
- Resolve the local bundle extension from FURNI_BUNDLE_EXT (".hab" default,
".nitro" for a stock Nitro client). The configured extension is written by
imports/repairs and preferred on lookup; the other stays a legacy fallback.
Imaging:
- Avatar proxy sends s-maxage=86400 + stale-while-revalidate (300/3600 for the
fallback path) so the edge can hold a render a day.
- Cloudflare cache rule now covers /api/imaging/ (verified MISS->HIT live).
- Document that bare /imaging is shadowed by the edge proxy to the renderer.
Ops/CI:
- docker-update.sh skips a CI-managed host (exit 0) instead of failing cron.
- ci-deploy.sh runs the cross-browser imaging gate (chromium+firefox+webkit)
against the candidate before cutover; DEPLOY_SKIP_CROSS_BROWSER=1 as escape.
- scripts/imager-health-alert.sh alerts (Discord) on imager up/down transitions;
runs from cron every 5 minutes.
Tests:
- Furniture bundle-ext resolver + FURNI_BUNDLE_EXT wiring.
- Imaging performance budget (bytes + warm latency).
- classifyDdos: /api/imaging/* stays in the api bucket.
Environment-independent contract tests (cache headers + source, CORS, OPTIONS
preflight, negative figure/badge tables, concurrency burst) run in both the
deploy gate and the cross-browser job.
CMS-only /imaging assertions (webp/avif transcode, animated-APNG passthrough,
s-maxage edge caching) live in cms-imaging.spec.ts and are skipped behind the
edge proxy; the public topology has its own public-routing.spec.ts smoke that
fails if the edge ever stops fronting the imager or starts shadowing the CMS
endpoints. A committed per-engine visual baseline pins the static render.
The cross-browser imaging job is now blocking (IMAGING_EDGE=1), so avatar
imaging on chromium/firefox/webkit is a required signal.
In production the edge proxy fronts /imaging to the imager container, so the
CMS route only answers when reached directly. Asserting it in the shared spec
failed against the public site in all three engines. Its behaviour is covered
by src/lib/imaging-route.test.ts; the e2e spec now only checks the endpoints
that are identical in both topologies (/api/imaging/avatar and the badge).
The deploy gate only installs chromium, so imaging had no browser-level
coverage. Add two Playwright specs:
- e2e/imaging/avatar-endpoint.spec.ts: HTTP contract for the avatar
(/api/imaging/avatar), default (/imaging) and badge endpoints — animated
APNG for a figure with an effect, the trailing-`.0` regression figure,
ETag/304 revalidation, and a 400 for a malformed figure. Runs in the
deploy gate automatically via the default Playwright config.
- e2e/imaging/avatar-render.spec.ts: loads the avatar in a real browser and
asserts it decodes at a non-zero size and animates. Navigates to the image
URL directly so production CSP `upgrade-insecure-requests` cannot rewrite
the request over a plain-HTTP host.
Add playwright.imaging.config.ts running both across chromium, firefox and
webkit, a `test:imaging` script, and a non-blocking CI job that installs all
three engines and checks the live site.
The hotel moved from .nitro to .hab bundles, so the imager must treat .hab
like .nitro and the public avatar endpoints must accept the figure strings
the client sends (including extra trailing segments such as `.0`, which the
old FIGURE_RE rejected with a 400).
- imager-upstream: relax FIGURE_RE, extend parseAvatarQuery test coverage
- api/imaging/avatar + /imaging: shared validation, placeholder and ETag/304
- api/imaging/badge: single-flight upstream fetch and conditional responses
- imager-http/imager-png: strong ETag helpers and APNG passthrough guard
- csp + next/image: unoptimized external images, tightened CSP
- deps: override postcss-selector-parser to ^7.1.6
- also fold in the pending drizzle draft snapshot and stray test cleanup
Route all Catalog Studio strings through the admin.studio namespace
(workspace, filters, review, inspector, quality, sync, scale32, organize
imports and page titles/subtitles) with English fallback, and add shared
StudioPage chrome plus a loading skeleton. Tighten mobile padding on the
header and sub-bars.
The badge GIF files exist at /var/www/Gamedata/c_images/album1584/ but the
/api/imaging/badge endpoint only looked in /swf/c_images/album1584/ (relative
to process.cwd()). Added GEDATA_IMAGES_BASE candidate and fetchCandidate
handler so badges render correctly in the admin UI and on the site.