- Add persistent disk cache for rendered avatars/badges (storage/imaging)
so repeats never touch the flaky local renderer and cached renders
survive upstream downtime
- Serve cache-first with stale-on-error; cut primary/fallback timeouts
from 10s/6s to 4s/4s so failing images cannot stall pages
- Avatar proxy now returns a graceful 200 silhouette instead of 502 when
no renderer can produce a figure, so no broken-image glyphs appear
- Badge endpoint becomes a caching proxy trying configured CDN, public
Habbo CDN and local /swf copy in order, and drops the fragile IP rate
limit that could blank badge streams
- Route all site badge images (profile, me, badges, apply pages) through
the cached proxy instead of hot-linking images.habbo.com
- Track referral attribution at registration via ?ref code with
same-IP and duplicate-pair guards
- Add daily login rewards with streak tracking, claim flow and
sendCurrency payout backed by RCON with DB fallback
- Add admin pages for referral settings and the daily reward schedule
- Add migration 0033 with tables, seed schedule, settings and ACL grants
- Add admin.referrals.* and admin.dailyrewards.* permission slugs
- Localize new copy in en, nl and it
Header and hero/stats counters each opened their own EventSource to the
online-count stream; a shared subscriber now opens a single socket and
multicasts to every mounted counter. The entrance count-up animation skips
its requestAnimationFrame loop when the user prefers reduced motion.
Add background_effect (aurora/particles), background_overlay tint and
opacity to the theme manager, rendered site-wide by ThemeVars on every
public page. Polish the home and register pages (hero mascot, live stat
pulse, date pills, photo strip, CTA band, theme-aware register intro,
i18n for home/register section).
Replace raw db.execute tuple casts with queryRows/rowsFrom/execResult/
affectedRows helpers from lib/db, drop redundant mysql2 casts on typed
query builders, and centralize per-test fakeForm into test/fake-form.
Update db mocks in tests so helpers resolve against mocked execute.
- password.ts: derive plain and salted digest detection from one DIGEST_SCHEMES
table instead of parallel hardcoded lists, so adding a family is one row.
- auth.ts: move 2FA challenge verification into twofactor-verification.ts and
the website login-log insert into website-login-log.ts, slimming the
NextAuth provider to orchestration only.
- deps: bump @formatjs/icu-messageformat-parser, @tanstack/react-query, jszip,
lucide-react, motion (patch/minor only). @types/react stay pinned per
pnpm-workspace.yaml; next-auth is already at the newest available (v5 beta).
Expand checkLogin to auto-detect and migrate every common retro CMS password
format to bcrypt on login:
- combined digests: md5(md5(pass)), md5(sha1(pass)), sha1(md5(pass)),
double sha1/sha256/sha512 and md5<->sha256/sha512 combinations
- salted digests of all families (md5/sha1/sha256/sha512) with embedded
salt using : $ @ _ separators, verifying both salt+pass and pass+salt
- plaintext fallback stays as the final catch-all
All formats verified on login and rewritten to bcrypt, so accounts work
whenever they come from any legacy CMS.
Legacy md5/argon2id hashes are now always upgraded to bcrypt on login, so
the CONVERT_PASSWORDS flag is no longer used. Drop it from env schema,
.env.example, the docker installer, and test mocks.
checkLogin now verifies and migrates all known password formats without
configuration: bcrypt, argon2id/argon2i/argon2d, unsalted md5/sha1/sha256/
sha512, double-md5 (UberCMS/Butterfly), salted md5 with embedded salt
(hash:salt, salt:hash, hash$salt), and a guarded plaintext fallback.
Every successful legacy login rewrites the stored hash to bcrypt, so the
CONVERT_PASSWORDS flag is no longer required (kept for deploy compatibility).
Theme Manager under /admin-next/hotel/theme-manager lets the owner save, apply, rename, delete, import, and export custom themes, plus set a custom site background by URL or upload. Themes are stored in WebsiteSetting/custom_themes JSON so they survive CMS updates.
The cleanup scan used to read every .nitro bundle in full and decompress
the large PNG texture just to confirm the file is structurally valid. On
directories with hundreds of thousands of bundles this took minutes, the
reverse proxy cut the request at its 30s timeoutable with an HTML 504, and
the panel then crashed with "Unexpected token '<'".
Validate bundles with a cheap header-only read (a few KB, no decompression)
that mirrors parseNitroBundle's byte layout; only files whose header looks
suspicious get the expensive full parse. Robust against downloads that
landed as an HTML error page, truncated or zero-filled files. The scan
drops from minutes to seconds on large nitro directories.
Also guard the panel against non-JSON (proxy error page / HTML) responses
so it reports a clear error message instead of a JSON parse failure.
Scan distinguishes fake, broken, and orphaned SWF/icon assets with age
metadata, deletes per asset kind, re-downloads broken nitro bundles from
configured sources, auto-cleans old fake leftovers, and exports a JSON
manifest. Adds rebuild and auto-clean API endpoints with audit coverage
and a housekeeping preview route under the hotel domain.
Verified: full vitest suite (2213 tests), typecheck, and biome all pass.
- Restore build_attempted=1 in ci-preflight.sh so the exit trap
removes the temporary image tag
- Remove publish-container.test.ts and its harness (publication
workflow and script were removed in fff284aa)
- Update deploy-workflow-contract and docker-build-contract tests
to assert that publication has been removed
Make /admin/catalog a full-screen catalog studio that replaces the old
listing plus separate [id]/builder-club detail pages:
- Embed CatalogManagerWorkspace on /admin/catalog with a Normal/Builder
Club toggle, Catalog Sync status, packages (normal), Organize imports
and a diagnostics link to /admin/studio/maintenance.
- Manage BC items directly in the studio Items tab (new BcItemsEditor,
CRUD via existing bc actions; /api/admin/catalog/items now serves BC).
- Inline editor: add pageTextTeaser field for both catalogs and remove
the legacy full-editor links.
- Remove the 'Open full editor' context action from the tree.
- Move catalog-items-table (dir + barrel) and catalog-translate-tab out
of the app route into src/components/admin/catalog and update all
importers.
- Keep /admin/catalog/[id], builder-club/[id] and /admin/catalog/maintenance
as redirects into the new studio; consolidate maintenance panels into
/admin/studio/maintenance and point the nav item there.
- Delete the old listing/table/tabs/forms and the standalone bc-manager.
Introduce getCachedAdminCount to cache un-filtered table count(*) queries in Redis for admin lists (starting with UsersPage), avoiding heavy full table scans on every request while keeping exact counts for search/filtered queries.
The 5-minute disk probe now reclaims storage automatically: from 85% it runs the gentle age-windowed Docker prune, from 90% it drops the age windows (docker-prune.sh --force: all unused build cache and unreferenced images, all stopped containers) so a mount can never silently max out. Alerts still fire at 85/90/95% and their hint now points at non-Docker growth when reclaiming is not enough. Force mode is reserved for the worker; deploys keep the gentle mode. Volumes are off-limits in every path.
Add a pure df parser (disk-usage.ts) with 85/90/95% threshold classification, a diskPressure() alert (Discord/email/alert_logs, severity escalates with fill), and a 5-minute host-side probe in jobs-worker.ts that raises one alert per crossing mount, cooldown-gated per mount+level. Real mounts only: overlay/tmpfs pseudo filesystems are ignored.
Add scripts/docker-prune.sh (build cache >72h capped at 4g, unreferenced images >7d, stopped containers >24h; never volumes), run it after every CI deploy and compose update, and schedule a nightly prune from the host-side jobs-worker. Tighten the deployment contract tests to assert the scoped-prune boundaries.
Mirror interactive batch runs into the import-job store so interrupted imports (restart, time-out, disconnect) can be resumed from Import History. Items are checkpointed as they settle (coalesced, serialized saves) and the mirror starts 'running' so the boot-time worker marks it 'interrupted' instead of double-importing; done items are never re-imported. Add bounded backoff retry for transient download/connection failures before marking an item failed, and point the client's time-out/network toasts at Import History.
Raise batch concurrency (furni 3->12, clone 10->12) with a Speed control next to Translate. Skip the SWF download when a .nitro bundle already exists on disk (color variants share the base nitro), and stop flagging that as a failed download.
- Render the table view through a virtualizer too, using a shared grid
template so the sticky header and rows keep perfect column alignment
- Keep semantic table/row/cell elements while virtualizing
- Cap the batch item-details list to the latest 60 rows (newest first)
- Coalesce per-item progress events server-side (120ms throttle) in both
the exact-import and clone SSE batch runners
- Add TTL-based caching for local index lookup, furnidata classnames,
catalog id set, nitro file presence and import stats
- Invalidate caches after furnace single/batch/clone imports
- Rewrite batch progress with elapsed time, rate and verification chips
- Virtualize the grid with @tanstack/react-virtual and replace the
Load more button with infinite scroll via an IntersectionObserver
- New src/lib/site-icons.ts: base64 data URIs for the 13 tiny classic
icons actually referenced in markup (100–2000 bytes), replacing extra
requests with inline payloads. home.png, dynamic flags and currency
sets stay on the filesystem.
- Default favicon is now served server-side as a base64 SVG data URI
(memoized), while a DB-configured custom favicon still takes priority.
- Icons render through <Image unoptimized>, so data URIs pass through
untouched on all affected pages (home, login, register, settings,
navigation, auth top bar, client loading).
resolveNitroFrame now matches spritesheet frame keys that carry a .png
suffix or namespaced naming, and isScale/scaleName preserve that suffix.
Broken source sprites (missing frames or references to icon artwork) are
skipped and reported instead of aborting the whole generation, and the
studio UI surfaces the skipped count.