Three failing test suites blocked CI. All three were test defects, not
application bugs.
Integration tests (integration/database.test.ts)
------------------------------------------------
The suite set NODE_ENV=test, which makes cache.cached() short-circuit both
its Redis read (src/lib/cache.ts:226) and its write (:249). A suite whose
stated purpose is exercising the real Redis path therefore never touched
Redis. Switched to NODE_ENV=development, the only non-production value
src/env.ts accepts, so the shared-cache code paths are genuinely covered.
Three assertions then needed correcting for real Redis semantics:
- `await cache.cached(...)` followed by `.resolves` can never hold: await
yields a value, not a Promise. Assert the value directly.
- A cached negative result is stored as the JSON encoding of null, so
`redis.get(key)` returns "null", not null.
- The news negative-cache key does not exist at all, so `ttl()` returned -2.
Now that the write path is live the key is created and the TTL assertion
holds as originally written.
UI tests (src/app/admin/prefixes/prefix-dialog.tsx)
---------------------------------------------------
The form-reset effect had `isOpen` removed from its dependency array. The
component returns null when closed, so the effect only ever ran on mount:
reopening the dialog no longer cleared the fields and a dismissed-but-
unsaved edit reappeared. Two tests in e2e/ui/unsaved-changes.spec.ts caught
this. Restored the dependency and documented why it is load-bearing.
The remaining edits in this branch drop stale biome-ignore comments that
suppressed useExhaustiveDependencies and noArrayIndexKey diagnostics. Where
the suppression had been load-bearing for behaviour, the underlying
dependency is now listed explicitly rather than silenced.
Verified: check (toolchain, audit, lint, i18n, typecheck), unit 3315
passed, integration 20 passed, UI 72 passed / 2 skipped.
- import-badge.ts, badges route, and badges edit route now resolve
ExternalTexts.json via getGamedataRoot() instead of the hardcoded
public/nitro-assets/gamedata/ path
- writeBadgeToExternalTexts creates the file (and parent dirs) when
missing, so fresh deployments no longer fail with ENOENT
- upload-import SQL maker now classifies furni via classifyFurni and
generates correct category sub-pages (imp_<catKey>) instead of
hardcoded imp_other
- Bump sw.js cache from v2 to v3
- Unregister all old service workers on page load
- Stop caching HTML/navigation (caused stale CSS refs after rebuild)
- Introduce redis-backed Prisma query cache (prisma-cache.ts) with per-model TTL
- Replace force-dynamic with revalidate=60 + generateStaticParams on news/[slug]
- Wrap article query with Redis cache (60s TTL)
- Upgrade service worker to v2 with dedicated API cache and stale-while-revalidate
for static assets
Move media storage from public/assets/images/media to storage/media
(outside Git and public/), so uploaded images, favicons and logos are
preserved across rebuilds and git clean. Add a shared media-storage helper,
update the upload/serve actions and API routes, and gitignore storage/.
- Split ManageClient into ManageSection, PresetsSection, TestSection,
ImportSection, ExportSection - each tab only renders what it needs
- Add loading/disabled states to preset, import, export, clear buttons
- Persist search and group filter via URL searchParams (survives refresh)
- Remove dead getAuditHistory export from actions
- Fix testRankPermission - remove unnecessary revalidatePath with JSON result
- Clean up unused errors/count variables in bulkImportPermissions
- Add 'loading' translation key to housekeeping section (en/nl)
Derive admin/public text colors from WCAG contrast, unify ThemeVars CSS emission, and keep navbar overrides in sync with readable vars.
Co-authored-by: Cursor <[email protected]>
Restore valid browser JS in theme-init, close mobile sidebar on navigation, and split autoDjForm title/trackTitle across locales.
Co-authored-by: Cursor <[email protected]>
- H1: Add missing sanitize() to help center content rendering
- H2: Tighten CSP by removing unsafe-inline/unsafe-eval from script-src;
move theme init to external JS file with meta tag for defaultDark
- M1: Add SSRF protection for radio API URLs (block private IPs)
- M2: Add rate limiting to SSO ticket endpoint (5 req/30s per user)
- M4: Document locale validation safety in i18n dynamic import
- L1: Truncate stacktraces in admin commandocentrum to first 20 lines
Beyond parity — the web-feasible versions of the "host-only" items plus
extras AtomCMS doesn't have:
- App-level abuse/DDoS guard (src/lib/services/abuse-guard.ts): counts
requests per IP and auto-adds flooders to website_ip_blacklist (enforced
by the access guard) + fires ddosDetected(). OFF by default, tunable via
settings. The iptables layer stays host-only; this is the real app-tier
mitigation. Access guard now also enforces the IP blacklist (cached).
- PWA: a themeable web manifest (src/app/manifest.ts) + a service worker
(public/sw.js, cache-first assets / network-first pages) registered after
hydration — the hotel is now installable.
- /api/health: DB + emulator(RCON) + runtime status probe.
- /developers: a public API documentation page covering every REST endpoint
with its method, path and auth requirement.
- jobs-worker: daily emulator JAR backup (runs host-side in the worker, like
AtomCMS's backup command) — copies + prunes; no-ops unless EMULATOR_JAR_PATH
+ EMULATOR_BACKUP_DIR are set.
Verified live (prod, amx_test): /api/health ok, manifest + sw served, docs
page renders, normal pages unaffected by the guard. tsc 0, vitest 49/49,
next build 0.
Make the template match the atom theme, not just approximate it:
- Add Tailwind v4 (+forms/typography plugins, postcss) and port the real atom
CSS (global.css + atom app.css) into globals.css: nav-item underline, currency
+ navigation icon classes, site-bg, card-base/hover-lift, text utils — asset
paths adapted to /assets.
- Copy the real theme assets (backgrounds, icons/currency/navigation, profile,
leaderboards) into public/assets.
- Rebuild the shell 1:1 from the atom Blade: TopHeader (currency pills +
user/admin dropdowns, auth-only), SiteHeader (header image + black/50 overlay,
logo+online+Nitro client / guest Login+Create-account CTA), Navigation (white
bar, nav-item + Community/Assistance dropdowns), Footer. ThemeVars injects the
DB-driven CSS custom properties into :root like app.blade.php. Layout uses the
atom body/site-bg + grid-cols-12 max-w-7xl content wrapper.
Verified: tsc 0, next build exit 0; curl confirms the atom markup, compiled CSS
references the assets, and background-light.jpg + currency/navigation icons all
serve 200.