Newly converted .nitro bundles now store their spritesheet as WebP VP8L
instead of PNG, so imports land much smaller without changing a single
pixel. The texture member and spritesheet.meta.image are both labelled
from the actual bytes, never from a caller's assumption.
- encode through sharp with lossless and exact, so colour hidden under
alpha 0 survives; this mirrors ImageSharp's TransparentColorMode.Preserve
- detect PNG/WebP by magic bytes and reject anything the client cannot
render, on create, download and upload paths
- keep the source format when deriving size-32 sheets, scaling composites
and editing metadata, so existing bundles are never silently rewritten
- report fidelity in the studio: the compression panel re-encodes with the
same options the importer uses, so it cannot drift and invent false
warnings, and shows PNG/WebP size estimates
convertSwfToNitro and buildSpritesheet are now async, so the worker, the
main-thread fallback and every import call site await them. PNG stays
supported for existing bundles and icon sidecars are untouched.
- fix(studio): stop markBatchDone infinite recursion so batches complete
- refactor(api): merge api-response into api and drop the duplicate module
- refactor(media): extract shared media loader and URL validator
- refactor(ui): extract shared LoadingSpinner for site and admin groups
- refactor(dates): consolidate raw date formatting into formatDate util
- refactor(logs): share a single generic log-list loader across tables
- refactor(theme): merge both ColorField components and reuse contrast helpers
- refactor(import): extract shared ImportErrorBanner and SearchInput
- chore(): remove dead theme-editor-tabs after inlining tab components
- verifyAndFixInteractionModesCount: paginated DB queries (500/batch)
instead of loading all items into memory at once
- withFurniDataLock: add 60s chain timeout to prevent deadlocks
when a lock holder stalls or crashes
- withGamedataLock: same timeout protection for gamedata locks
- conversion-pool: add 60s per-job timeout, fall back to main thread
- furni/batch: abort signal + post_import progress events + skip
post-import steps when client disconnects
- furni/batch-regen: abort signal + early exit when disconnected
- clone/sync-all: pre-fetch furnidata once per source instead of
per item (eliminates 2000+ redundant fetches)
- sse-client: add 60s idle timeout to prevent infinite hangs
Every write path now sets offer_id = catalog_items.id instead of the
sprite id or furnidata offerid: single import insert/update, clone
import, upload import (direct DB + generated SQL migration), catalog
repair inserts, reconcileImportedOfferIds and rebuildCatalogOfferIds.
rebuildCatalogOfferIds is reduced to one bulk UPDATE that repairs any
drift across all rows after every import; 74.838 legacy rows were
repaired on the live database with this change.
Generate localized FurnitureData_<lang>.json files from the master
FurnitureData.json + official Habbo hotel translations. Fetches per-hotel
furnidata with in-memory cache (6h TTL), maps by classname (* variant
aware), and overrides name/description where a translation exists.
Full rebuild after every batch/batch-regen/clone import and as admin
GET ?action=build-languages; single imports patch incrementally via
patchLocalizedFurniDataEntries. Failures are best-effort and never fail
the import itself. Files are written to every configured gamedata write
path (primary + mirrors) so /gamedata/config/FurnitureData_<lang>.json
is available for per-language Nitro clients.
New service: src/lib/services/furni-data-i18n.ts with
buildLocalizedFurniDataFiles, patchLocalizedFurniDataEntries,
FURNIDATA_LANGUAGES (nl/en/de/fr/es/tr/it) and applyTranslationsForTest.
The emulator sends items_base.sprite_id to the client and Nitro resolves
the furniture by looking up that id in FurnitureData.json. Legacy rows
where sprite_id drifted from the id made the client render a completely
different item (e.g. a wired or custom asset instead of the purchased
furniture).
Add verifyAndFixSpriteIds() which repairs any row whose sprite_id no
longer matches its id while the local furnidata carries the item under
that id, and run it after every import path (single, batch, batch-regen,
clone) next to the offer_id rebuild.
Interaction detection:
- Trust SWF-derived sit/lay/stand flags only when the logic XML actually
contains action data (new hasActions metadata); otherwise fall back to
keyword detection so custom furni without <action> nodes are still
classified correctly
- Add French/Dutch/German/Spanish/Italian keywords (chaise, banquette,
stoel, silla, sedia, stuhl, tafel, mesa, ...) to sit/lay/stand detection
with token-boundary matching to avoid false positives like bedside_table
- Unify interaction_modes_count priority: mechanic fixed modes, then raw
animation state count, then sit/lay fallback, then keyword default
- Detect mechanic type even when real flags are not used
Catalog integrity:
- Skip duplicate catalog_items inserts in clone and upload imports
- Re-check live catalog rows before applying generated repair SQL
- Add rebuildCatalogOfferIds() which rebuilds every catalog_items.offer_id
from the local FurnitureData.json (matched by entry id, then classname)
and run it after every import path (single, batch, batch-regen, clone)
After every single import, batch import and batch-regen, cross-check all
items_base rows against the local FurnitureData.json: entries whose
spriteId drifted are corrected to the DB id, and missing entries plus
real id conflicts are reported in the API/SSE response. Entries that are
missing entirely are counted (rebuilding them needs SWF/nitro metadata).
Extend the post-import backfill to also copy missing _icon.png files
into /var/www/Gamedata/icons (in addition to nitros into
bundled/furniture) so all imported furniture shows an icon in-game.
Report copied nitros and icons separately in the API/SSE responses.
After each single import, batch import and batch-regen, copy every
.nitro file that is missing from /var/www/Gamedata/bundled/furniture so
the emulator can render all imported furniture even when the mirror write
was skipped. Reports the copied files in the API/SSE response.