The flush after every 400-item chunk was running expensive operations
(reconcileOfferIds, rebuildCatalog, verifySpriteIds, rcon updates) which
caused the import to hang after ~800 items. Now only the final chunk
triggers the full consolidation. Also raised the per-request limit from
500 to 5000 items.
- Add 'Clone all missing' button for clone sources with confirmation dialog
- Show per-item status badges: nitro, furnidata, catalog entry
- Add status filters: missing furnidata, missing catalog entry
- Add ETA and percentage to batch progress bar
- Add retry button for failed items after batch import
- Auto-translate all languages after clone-all completes
- Fix statusFilter 'all' bug that incorrectly filtered imported items
- Bulk-load FurnitureData + catalog references for O(1) per-item checks
- Add theme-blocks.ts registry with 24 themeable blocks across 4 categories
- Extend resolver to resolve blocks, layout, effects, media, and custom CSS per scope
- Add data-theme-block attributes to all site layout blocks
- Extend ScopedThemeVars to generate CSS for block visibility, layout vars, effect vars, media vars, and custom CSS
- Rewrite admin UI with 6 tabs: Colors, Blocks, Layout, Effects, Media, Custom
- Extend server actions to save/load all new setting types
- No database migration needed - uses existing theme_scope_values table with prefixed keys
- Add theme_scopes and theme_scope_values database tables for scoped themes
- Implement theme resolver engine with inheritance: global > site > module > route
- Add module detection for 20+ routes (shop, guilds, radio, news, etc.)
- Create admin UI at /admin/theme-builder with scope tree and color editor
- Add ScopedThemeVars component for injecting scoped CSS via data-attributes
- Add ThemeScopeDetector client component for runtime module/route detection
- Add site-resolver for multi-site domain detection
- Add /api/themes/export endpoint (JSON, CSS, variables formats)
- Add /api/themes/export/embed.js for external integration widget
- Add server actions for full CRUD on scopes and theme values
- Add admin nav link and EN/NL translations
- Stack the /me dashboard hero (avatar + info) on small screens instead of
crowding them side by side; center the avatar and info on mobile
- Respect prefers-reduced-motion in the Reveal scroll animation
- Make the desktop pitch-in (NavDropdown) and mobile menu more consistent;
give the mobile menu a max-height with overscroll containment so long
navs scroll instead of overflowing on small phones
- Add safe-area insets for notched devices on header, nav and footer
- Polish whole-link content-cards (community tiles) with hover lift and a
visible focus ring
- Enforce a comfortable min touch height on all .btn buttons
- Enhance SurfaceCard with refined borders, layered shadows, and polished
primary-tinted header with an icon container
- Update homepage blocks (hero, features, stats, login, news, photos) for
proper mobile/tablet/desktop responsiveness and tighter mobile spacing
- Improve content-card, stat-block, card-grid and card styling with subtle
depth (layered shadow + inner hairline) and smoother hover states
- Refine site header, footer, top header and site layout spacing for small
screens while keeping a consistent professional look on large screens
- Add min-tap-height targets and responsive typography on mobile
The landing page showed the same information more than once. Drop the second
avatar grid (latest users) and its DB query, move the online users grid into
the left column, remove the register banner card and the bottom join CTA so
registration is only offered once in the hero, and show the online count a
single time in the hero badge instead of also in the stats row. The online
users query now fetches 12 rows instead of 30, saving bandwidth on every
uncached render. The avatar presentation contract test now expects one
thumbnail call site on the home page.
Fetch the Nitro client iframe with high priority so the browser starts the
game document before competing resources, and replace the bare /client
spinner with a branded boot screen. Preconnect and eager loading were already
in place; typing support for the iframe fetchPriority prop is added in a
React type augmentation.
Rework the register terms block into a single clickable accept control with
a custom check state, error shake and inline hint, and dim the submit button
until the terms are accepted. The register page gets labeled sections
(account details / credentials), a corrected banner overlay, translated
show/hide toggles and a captcha slot that reserves height to avoid layout
shifts. English is the source of truth; other locales fall back to it.
Reuse the outstanding auth_ticket instead of minting a fresh one on every
/client load, so reloading the page or opening a second tab no longer
invalidates a game session that is still connecting. New tickets are minted
with a guard against the previously-read value so concurrent launches
converge on the same ticket.
Revoke the auth_ticket when signing out (toolbar, header and sign-out
everywhere) so a leaked ticket can no longer be replayed against the
emulator, and prevent SSO leakage via referral by setting no-referrer on the
client iframe. Strip all whitespace from the ticket prefix and build the
launch URL through a tested helper that handles query strings, existing sso
params and URL fragments correctly.
Wrap next/link in a shared Link component that ships prefetch=false by
default, so no route is ever prefetched (viewport or hover) anymore, and
drop the DNS prefetch hint. Removes hidden background requests that were
the source of intermittent issues.
- update-Nitrov3.sh: build CMS into .next-staging and swap atomically so a
failed build never takes the live site down; auto-merge new variables
from .env.example; validate env for duplicates/broken lines; restart the
emulator/CMS only when rebuilt or unhealthy; fix step renumbering
- next.config.ts: support NEXT_DIST_DIR for staged production builds
- fix all TS errors (unused imports, missing tryDownloadCandidates helper)
so tsc and the production build pass clean
- add Dockerfile/.dockerignore and switch docker-compose to a CMS container
- include prevailing UI/refactor changes (SurfaceCard, ticketing, tsconfig)
Drop the hardcoded FALLBACK_HOTEL_NAME ("Atom") preset and the brand.ts
module. HOTEL_NAME is now a required env var: if it (and the CMS hotel_name
setting) is missing the site fails validation at startup/build with a clear
message instead of silently rendering a placeholder hotel name.
resolveHotelName() resolves CMS hotel_name -> required HOTEL_NAME only.
Callers that used the preset (api/home route catch branch, CMS settings form
default, mobile-nav/logo-generator prop defaults) now use the configured name
or an empty default; the real name is already passed in by server parents.
Replace the three near-identical public card primitives (ContentCard for
content pages, SectionCard for auth/account, and the new SurfaceCard) by
merging SectionCard into SurfaceCard, which now supports an optional header
(title/icon/action). Every remaining ad-hoc inline `rounded-2xl border`
card across (site) is converted to SurfaceCard, preserving each card's unique
visuals (background images, blur, gradients) via the style passthrough.
Net result: the public site uses exactly two card components — ContentCard
(CMS/content pages) and SurfaceCard (everything else) — and the shadcn Card
in components/ui/card.tsx is left untouched for admin.
Also deletes the now-unused components/home-section.tsx.
Introduce components/surface-card.tsx (Card + CardBody) mirroring the
.content-card / SectionCard token set, and use it on the (site) pages that
still hand-rolled card markup: me, search, and verify. This puts every
public page on one of the shared card components (ContentCard, SectionCard,
or SurfaceCard) for consistent radius/shadow/border.
Unify the public site by making SectionCard and the verify status card use
the same --radius-lg / --shadow-card tokens and primary-tint header as the
existing CSS .content-card used by all content pages. This makes the entire
(site) group visually consistent without rewriting every page, and keeps the
shadcn Card in components/ui/card.tsx (used by admin) intact.
Unify the per-section neon gradient headers (blue/green/purple) into a
single consistent SectionCard component with a calm surface header and
hairline divider, constrain the page to a centered max-w-6xl container,
and soften the hero/cards with rounded-3xl corners and subtle shadows.
- Send the verification email after the response via after() so it
never blocks sign-up
- Invalidate the cached login lookup right after account creation so
the automatic sign-in always finds the fresh row
- Auto sign in with the submitted credentials and go straight to /me,
with a fallback to /login?registered=1 if sign-in is refused (e.g.
email verification required)
- Cache the register page's online/latest user queries to cut DB load
under traffic
- Fix terms checkbox label double-toggle cancelling the selection
- Add pages.register.redirecting translation to all locales
The maintenance "Fix alles" now has a checkbox to include or skip the
duplicate catalog_pages merge, so admins can choose whether to run it.
removeDuplicates takes an includePages flag and fixEverything threads it
through to the action.
Furni imports can now choose whether to translate (per import) and which
languages to build, instead of always rebuilding all 13 FurnitureData_<lang>
files. The studio header also has a global switch that persists the
furnidata_translate_enabled setting, seeding the per-import default.
The maintenance "Fix alles" now collapses catalog_pages that share the same
caption_save + parent_id into a single survivor (moving catalog_items and
reparenting child pages before deleting duplicates), so the catalog tree
never renders double links. The duplicate-page count is also surfaced in the
health panel.
- Move /admin/import/* tools under /admin/studio/* and add studio nav, layout and tabs
- Add shared catalog maintenance panel plus a /admin/studio/maintenance tab
- Make furnidata reconciliation overwrite conflicting entries with the
DB-authoritative items_base classname/id (surfaced via fixedConflicts)
- Add furnidata_translate_enabled setting to skip the heavy localized
furnidata build during import (manual build-languages still forces it)
- Update staff smoke contract test for the studio hub
- Add EMULATOR_MODE=api option with HTTP transport mirroring RCON payload
- buildLocalizedFurniDataFiles now falls back to LibreTranslate for customs (incl. pl/ru/ar/ja)
- Remove Organiseer import button from studio and Reorganize Catalog from import page
- Delete organize API route and PUT handler; remove organizeCatalogAll from catalog-repair.ts
- Increase imported-furni list perPage from 50 to 500
organizeCatalogAll no longer pulls every items_base row into the catalog.
It now touches only items whose catalog entry lives on a page under the
Imported Furniture parent (re-homing/re-pricing onto line/category
sub-pages). Items are never newly added. Button renamed to 'Organiseer
import'.
Add a fully automatic "Organize all" action in the Studio that organizes
the entire database into catalog pages:
- Create a dedicated sub-page per furniture line (e.g. weebz, habbox) from
FurnitureData.json, falling back to the auto-detected category.
- Add missing purchasable items to the catalog on their line/category page.
- Re-home and re-price existing catalog entries onto their correct page.
- Pages are created automatically when missing; no manual selection needed.
Includes the organizeCatalogAll service, a POST /api/admin/import/furni/organize
endpoint, and the Studio button.
- Add /api/admin/import/furni?action=live-reconcile POST endpoint that reconciles FurnitureData.json with items_base without triggering RCON hotel reload
- Add 'notImported' status filter in studio to show all non-imported furniture items
- Useful for verifying imports and seeing what's missing after furniture import
- New auto-interaction.ts: detects canSit/canLay/canStand from classname/name keywords
- Maps keywords to interaction types (bench, chair, bed, table, etc.)
- Returns clickLimit and interactionModesCount for proper click/interaction limits
- Nitro editor: added auto-detect button with preview of detected settings
- furni-import.ts: auto-sets interaction_modes_count during import
- upload-import.ts: auto-detects for direct insert and SQL migrations
- clone-import.ts: auto-detects when cloning from other hotels
After a batch import the progress panel now shows a verification
summary (offer_id fixed, furnidata ids fixed / missing / conflicts,
nitros and icons synced, folders chowned). Single imports include the
key counts in the success toast.
- Card-shaped loading skeletons with shimmer text lines
- Hover lift and accent shadow on furni cards
- Subtle dotted floor pattern behind previews and detail image
- Sticky action footer in the detail drawer
- Theme-aware slim scrollbars inside the admin panel
- Nicer empty state with icon tile
- Add debounced live search with keyboard shortcuts (/ focus,
Ctrl/Cmd+A select all, Esc clear selection)
- Add category filter and sort controls (name, classname, category,
imported-first)
- Add grid/list view toggle with a compact list table
- Add regenerate .nitro action for missing-nitro items
- Show result count in the header and a missing-nitro shortcut chip
- Fix invalid nested button elements in furni grid cards that broke
card borders, hover styles and selection rendering
- Fix stale-closure bug where switching furni source fetched from the
previously selected source
- Add progress bar plus Cancel/Dismiss controls to batch imports
- Make the detail drawer an overlay on small screens
- Add clear-search and clear-selection actions in the toolbar
- Use pixelated image rendering and keyboard focus rings on cards
- Cache read-heavy public API routes via redisCache (leaderboard, values,
shop, articles, photos, guilds, teams, staff, users, home, radio, badges)
- Add single-flight and bounded-memory cache layer with unit tests
- Parallelize independent DB queries on search, rares, shop, staff, polls
and profile pages
- Push radio points leaderboard aggregation to SQL with a LIMIT
- Split studio-client and import-furni-client into focused modules
- Clean up next.config.ts
Allow the admin Studio to browse and import furniture from custom retro
hotel sources, not just official Habbo furnidata.
- GET /api/admin/import/furni?source=<id> lists a clone source's
furnidata (via getCloneList) and returns a per-item iconUrl from the
source's iconBaseUrl so previews render correctly
- Studio client gets a source selector dropdown (official Habbo plus all
configured clone_sources) that resets the selection and reloads the
list when switched
- Single and batch imports now send sourceId so SWF/nitro/icon assets
are fetched from the selected hotel's CDN
- Preview images prefer the source iconUrl with the existing fallback
chain; header shows the active source name
Add a full-viewport /admin/studio workspace that unifies the import and
catalog systems into a single automated flow:
- Furni library browser against official Habbo furnidata with search,
type/status filters, live previews and pagination
- Catalog structure rail showing where imported furniture lands
- Detail drawer with automatic placement preview (category + price via
classifyFurni/autoPriceFurni) and one-click import that downloads
assets, converts SWF to nitro, writes items_base, updates
FurnitureData.json and creates an auto-priced catalog entry
- Batch auto-import with SSE progress and RCON cache refresh
- Nitro editor integration and imported-item deletion
Extract pure auto-catalog helpers (CATEGORY_PAGE, CLASSNAME_RULES,
classifyFurni, autoPriceFurni) into a client-safe module shared by the
server pipeline and the Studio UI so the preview always matches what the
emulator import applies.
- Add hcaptcha_site_key and hcaptcha to captcha_provider options in admin settings
- Update captcha.ts server-side verification for hCaptcha (new endpoint + secret key)
- Add hCaptcha widget rendering in register-form.tsx
- All TypeScript and Biome checks pass
- Remove age verification (18+) checkboxes from register form
- Terms checkbox remains
- Turnstile CAPTCHA stays further down in form
- All TypeScript and biome checks pass
- Terms and age verification checkboxes now side by side
- Added onKeyDown handlers for keyboard navigation (a11y)
- Improved text clarity with explicit var(--color-text-readable) usage
- All TypeScript and biome checks pass
- Add termsAccepted and ageVerified columns to User table
- Update register schema with new boolean fields
- Fix register form age verification checkbox (th -> t)
- Fix furni-import spriteId declaration order
- Fix batch route variable naming (id -> spriteId)
- Fix catalog-audit import path and ensure correct types
- Hardened import with per-item id conflict checks
- Added audit option for FurnitureData.json spriteId conflicts
- Updated tagline to include Leeftijdsvereiste: 18+