Chat tagging:
- Any @user is a visible tag in chat bubbles (the .mention-tag CSS never
existed, so highlighting was invisible); self/alias mentions get a gold
emphasis. Fixes cross-room tags not being highlighted.
Mentions window:
- Redesigned: unread count in the header, restyled filter chips + a refresh
button, CSS-driven list/date-groups, adaptive height (compact when few,
capped + scroll when many), polished empty state.
- Rows: framed avatar (friends-list head crop so the face is never clipped),
per-row unread dot, type marker, icon action buttons (goto / remove).
- Re-requests from the server each time it opens.
Autocomplete:
- Never suggests the viewer themselves; suggests room users + online friends +
aliases.
Notifications:
- Mention toast removed; mentions flow through the client's standard
notification stream via a dedicated mention bubble (avatar + actions) in the
default position. EVERY received mention surfaces (independent of the generic
info-feed toggle, gated only by mentions_ui.enabled).
Refactor (behaviour-preserving):
- Centralised @-token classification in api/mentions/mentionTokens.
- Moved mentionsFormat -> api/mentions, useMentionActions -> hooks/mentions.
- Extracted ChatInputView @-autocomplete into a tested useChatMentions hook +
pure helper; removed the dead duplicate useMentionAutocomplete.
useFurniEditor gains importText(id) + importResult (10049 round-trip); the
edit view shows an 'Import from Habbo' button that fills Display Name/
Description with the official text for review before Save (nonce-guarded,
classname-matched), with an inline result note.
- Restyle to match the editor: slate palette, rounded card, search bar
with icon + focus ring, teal filter chips, larger furni thumbnails,
loading skeleton + empty state.
- Live search (350ms debounce) with clear button; filters/sort apply
immediately.
- Pagination: first/prev/next/last + page-jump input + 'Showing X-Y of Z'
(was Prev/Next only across 3002 pages).
- Header sort now queries the server (sortField/sortDir) instead of
reordering only the 20 visible rows; useFurniEditor.searchItems passes
the sort through.
The server now mirrors the furnidata display name into
items_base.public_name, so on updateFurnidata patch selectedItem.publicName
immediately; the auto detail re-fetch that follows agrees with the DB
value (no flicker). Not applied to revertFurnidata (revert restores the
previous DB name).
useFurniEditor gains importText(id) + importResult (10049 round-trip); the
edit view shows an 'Import from Habbo' button that fills Display Name/
Description with the official text for review before Save (nonce-guarded,
classname-matched), with an inline result note.
- Restyle to match the editor: slate palette, rounded card, search bar
with icon + focus ring, teal filter chips, larger furni thumbnails,
loading skeleton + empty state.
- Live search (350ms debounce) with clear button; filters/sort apply
immediately.
- Pagination: first/prev/next/last + page-jump input + 'Showing X-Y of Z'
(was Prev/Next only across 3002 pages).
- Header sort now queries the server (sortField/sortDir) instead of
reordering only the 20 visible rows; useFurniEditor.searchItems passes
the sort through.
The server now mirrors the furnidata display name into
items_base.public_name, so on updateFurnidata patch selectedItem.publicName
immediately; the auto detail re-fetch that follows agrees with the DB
value (no flicker). Not applied to revertFurnidata (revert restores the
previous DB name).
- Chat input @ autocomplete: typing @ shows online users (room users +
online friends + room aliases) with avatars; arrows/Tab/Enter to pick.
- Any valid @nick token is highlighted blue in chat bubbles (like @all),
giving visual feedback that it is a recognised mention.
- Side notification toast on a received mention: sender avatar (from the
new senderFigure wire field) + message + dismiss; dismiss marks it read
so the toolbar unread badge updates. Auto-hides after 8s.
- IMentionEntry/parsers carry senderFigure end to end.
On catalog open, re-fetch the custom furnidata chunk (custom/imported.json5) via
SessionDataManager.mergeFurnitureDataFromUrl() and feed the new entries to
RoomContentLoader.processFurnitureData(), so furniture imported from the admin
panel appears without a full client reload.
setTab updated currentTabCode/currentFilter but never reset isCreatorOpen, so after opening the room creator, clicking another navigator tab changed the active tab code while the creator view stayed rendered (NavigatorView renders the search and creator views mutually exclusively based on isCreatorOpen). This left users stuck in the creator unable to switch tabs. Reset isCreatorOpen to false in setTab so selecting any tab also closes the creator.
Runtime-loaded visual re-skin system (no client rebuild, real themes never
hit git). A theme = a folder on the server (theme.base.url) with a manifest +
CSS "pieces"; each piece is toggled from Settings > Themes (checkboxes). A
broken/404 piece auto-falls back to the default (per piece). Hotel-wide default
via ui-config theme.default (+ theme.default.pieces), per-user override in
localStorage (same pattern as the catalog style toggle).
- api/theme/ThemeManager: fetch index/manifest + inject/remove <link> + fallback
- hooks/theme/useThemes: state + persist + default-from-config + live apply
- components/theme/ThemeApplier: applies on boot (mounted in MainView)
- UserSettings: General/Themes tabs with theme selector + per-piece checkboxes
- custom-themes/: reference template (demo theme "Neon Viola" + README)
- .gitignore: public/custom-themes/ (real themes are never committed)
Player experience:
- Tiered win celebration overlay (WheelWinReveal): quiet message for the
"nothing" slice, lighter reveal for common prizes, full confetti +
jackpot glow for rare ones. Rarity classified client-side by type +
amount (wheelPrizeTier), shared icon rendering (wheelPrizeIcon).
- Three-phase spin motion (wind-back -> overshoot -> settle) with a
reduced-motion fast path; responsive wheel scaling via ResizeObserver.
Reveal-timing fix:
- The server pushes the refreshed winners list (which already contains the
just-won prize) the instant it answers the spin, ~5s before the wheel
stops. useFortuneWheel now buffers that update mid-spin and flushes it in
finishSpin so the prize is no longer spoiled in the winners panel.
- handleTransitionEnd only reacts to the wheel's own transform transition,
so a child icon's bubbling transitionend can't advance the spin phase
machine early.
Prize editor (admin):
- Add/Remove prize buttons in FortuneWheelSettingsView. New rows carry a
negative temp id collapsed to 0 on the wire (server inserts); removed rows
are simply omitted (server soft-disables). Requires the matching emulator
change to WheelManager.savePrize / WheelAdminSavePrizesEvent.
i18n: wheel.win.* and rarevalues.editor.add/remove in en/it/nl.
The hook is the useState/useMessageEvent variant; the leftover
useQueryClient().invalidateQueries call required a QueryClientProvider
the unit test didn't supply (6 failures). The FlatCreatedEvent handler
already re-sends the search composer, so the invalidate was dead code.
Move favourite room ids out of the useBetween navigator store into a
dedicated Zustand store. useNavigatorFavourite(roomId) subscribes only
to s.ids.has(roomId) (a boolean), so a FavouriteChangedEvent for one
room no longer re-renders every favourite-aware view. apply() returns
the same state reference when membership is unchanged.
Aggiunge un checkbox nelle impostazioni utente per scegliere lo stile del
catalogo (classico vs moderno) + flag globale catalog.classic.style in
ui-config.json come default per tutti. Override per-utente in localStorage.
Adopt upstream wheel redo (Settings popup gated by acc_wheeladmin,
RareValues becomes view-only) and the radio enable/disable config gate.
Drop the broken orphaned duplicates under user-settings/fortune-wheel
and user-settings/rare-values (wrong relative import depth, unused,
failed typecheck). Soundboard / radio / background editor untouched.
Visual polish, first wave:
- NavigatorEmptyStateView: replaces the bare "No rooms found" text with a
centered icon + message + a Create-room CTA. Reuses existing i18n keys
(navigator.search.returned.no.results / .roomsettings.moderation.none /
.createroom.create) so no new localization entries are needed.
- NavigatorSearchSkeletonView: animate-pulse placeholder rows shown while a
search is in flight and no result is cached yet (matches the HK dashboard
skeleton pattern). Replaces the NitroCard.Content spinner overlay for the
result list.
Bug fix bundled in: NavigatorSearchView called useNavigatorSearch() a second
time purely to read searchResult for its input-sync effect. Since the hook is
not a useBetween singleton, that registered a duplicate NavigatorSearchEvent
listener AND fired a duplicate NavigatorSearchComposer on every search.
NavigatorView now owns the single useNavigatorSearch() call and passes
searchResult to NavigatorSearchView via prop.
Test maintenance: useNavigatorSearch.test.tsx was written for the original
useNitroQuery implementation, which upstream reverted (05d71dd1) to
useMessageEvent + useState. Removed the dead QueryClient scaffolding, fixed
case 1 (assert no fetch starts with empty tab), dropped case 7 (the query
invalidator no longer exists). 6 cases, all green.
Full suite 471/471. Typecheck: only the environmental renderer-mismatch
errors (soundboard / rare-values / floorplan APIs absent from the linked
renderer), none in navigator files.
useNavigatorSearch had two gaps its tests cover:
- with no active tab the query is disabled, but a NavigatorSearchEvent still
updated the data; now such events are ignored until a tab is active
- a newly created room (FlatCreatedEvent) now invalidates the
['navigator','search'] query and refetches the current search
Fixes the 2 failing useNavigatorSearch tests; full suite 472/472.