Commit Graph

196 Commits

Author SHA1 Message Date
simoleo89 d45a0fb8b0 feat(furni-editor): optimistically reflect mirrored public_name on save
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).
2026-06-06 17:42:42 +02:00
simoleo89 7e72e34088 feat(furni-editor): updateFurnidata/revertFurnidata hook actions 2026-06-06 17:42:41 +02:00
duckietm f4d41dd3c9 Take #2 Desktop cacta 100% 2026-06-05 14:32:55 +02:00
duckietm 9982c96b63 🆙 Bug fixed in localstorage 2026-06-04 13:50:40 +02:00
duckietm 59ed27b727 Revert "🆙 Bug fixed in localstorage"
This reverts commit 47453db5ee.
2026-06-04 13:43:29 +02:00
duckietm 47453db5ee 🆙 Bug fixed in localstorage 2026-06-04 13:43:04 +02:00
duckietm 7007752e91 🆙 Mention Now in UI-Config and UITexts and not hardcoded 2026-06-04 11:32:55 +02:00
DuckieTM a4137af4fe Merge branch 'main' into Dev 2026-06-04 10:45:59 +02:00
medievalshell 0df810c556 feat(mentions): @ autocomplete, blue @nick, avatar notification toast
- 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.
2026-06-04 01:18:26 +02:00
medievalshell f8e943d262 feat(catalog): live-merge imported furni on catalog open
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.
2026-06-03 23:28:33 +02:00
DuckieTM 155d85d3e7 Merge pull request #194 from duckietm/Dev
Dev
2026-06-03 16:42:51 +02:00
DuckieTM c76fbf7ea8 Merge branch 'main' into feat/command-autocomplete-refactor 2026-06-03 16:33:58 +02:00
Medievalshell 62062cec9e Merge branch 'duckietm:Dev' into Dev 2026-06-03 13:06:25 +02:00
medievalshell 8b44aee0df Fix navigator tabs locking after entering room creator
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.
2026-06-03 13:04:07 +02:00
simoleo89 f506b83387 feat(chat): improve command autocomplete and command alerts 2026-06-02 18:34:50 +02:00
simoleo89 c576c6185a feat(mentions): use dedicated mention chime sample 2026-06-02 15:04:12 +02:00
simoleo89 49d3bde50a feat(mentions): richer inbox — filters, date groups, type badge, relative time, per-row actions, highlighted preview 2026-06-02 15:04:12 +02:00
simoleo89 c67c90d4c1 feat(mentions): inbox window, toolbar badge, chat-history tab, ui-config + i18n 2026-06-02 15:04:11 +02:00
simoleo89 afb8100300 feat(mentions): client api types, store, snapshot + message hooks 2026-06-02 15:04:11 +02:00
DuckieTM 67d53fde3a Merge pull request #181 from simoleo89/feat/fortune-wheel-improvements
feat(fortune-wheel): celebration reveal, spin animation & prize editor add/remove
2026-05-31 15:44:42 +02:00
DuckieTM 7e0d70a7e6 Merge branch 'Dev' into Dev 2026-05-31 15:43:52 +02:00
medievalshell 8097344561 feat(theme): runtime custom theme ecosystem (graphics-only)
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)
2026-05-31 14:39:59 +02:00
simoleo89 ccebcad8a8 feat(fortune-wheel): celebration reveal, spin animation, prize editor add/remove
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.
2026-05-31 10:47:51 +02:00
simoleo89 7f9e31eec3 fix(navigator): drop vestigial TanStack invalidateQueries from useNavigatorSearch
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.
2026-05-31 01:06:44 +02:00
simoleo89 e610cfeef4 feat(navigator): reactive favourites via fine-grained store (P3)
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.
2026-05-31 01:03:31 +02:00
Life b17cd891b3 Merge branch 'duckietm:main' into feat/navigator-p4-visual-wave1 2026-05-30 09:38:44 +02:00
medievalshell dc0a8f965e feat(catalog): toggle stile catalogo classico/moderno
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.
2026-05-29 23:36:06 +02:00
medievalshell 643e558aa8 Merge remote-tracking branch 'upstream/Dev' into Dev
# Conflicts:
#	src/hooks/navigator/useNavigatorSearch.ts
2026-05-29 23:06:48 +02:00
duckietm d0c11f047a 🆙 Complete rebuild of toolbar / catalog / inventory make it 100% mobile friendly Take #1 2026-05-29 11:30:17 +02:00
duckietm fbcda88cd3 🆙 Update Rare-Value page and fix the loading of the json 2026-05-29 08:31:18 +02:00
medievalshell b360595539 Merge upstream/Dev: fortune wheel settings popup + radio toggle
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.
2026-05-28 22:28:00 +02:00
simoleo89 3bce0c0191 feat(navigator): empty-state + skeleton views, fix double search fetch (P4 wave 1a)
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.
2026-05-28 18:02:48 +02:00
duckietm 47e8338570 🆙 Wheel of prizes ! 2026-05-28 16:27:48 +02:00
medievalshell 7a0b57e267 fix(navigator): ignore search events while disabled + invalidate on FlatCreated
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.
2026-05-28 15:39:46 +02:00
DuckieTM 475c71af2e Merge branch 'Dev' into Dev 2026-05-28 13:49:43 +02:00
duckietm 05d71dd163 🆙 Small fix for the navigator
This mirrors what the old god-hook used to do and what the rest of the codebase still uses for everything else. The TanStack one-shot listener pattern (awaitNitroResponse registers a listener, awaits one matching response, removes itself) is fragile against renderer-bundle quirks — the parser fires but the listener never matches, so the promise never resolves and query.data stays undefined forever. That's exactly the symptom you saw: server logs show the response arriving, client UI stays blank.
2026-05-28 13:46:44 +02:00
medievalshell acb3dd7ef1 feat: hotel radio widget (client-side, multi-station)
Adds a compact collapsible radio widget (top-left) that plays internet
radio streams with the HTML5 Audio API — no server/renderer changes.

- station list loaded from a JSON5 config file (loadGamedata: JSON + JSON5),
  shipped as radio-stations.json5.example so each hotel fills in its own
- shows the selected station + a dropdown (3 visible, scrolls if more) to
  switch; volume slider; animated equalizer + LIVE indicator
- first station autostarts quietly (5%) on load, with a resume-on-first-
  gesture fallback for browser autoplay policy
2026-05-28 10:20:15 +02:00
medievalshell 4833ab8447 feat: soundboard pads can load from a JSON5 file (DB fallback)
When the server (soundboard_sounds table) returns no pads, the client now
loads them from a JSON5 config file (loadGamedata accepts plain JSON and
JSON5). Useful when the DB / CMS isn't set up yet.

File-defined pads play locally for the clicker; DB-backed pads still go
through the server broadcast so everyone in the room hears them. Ships a
radio-style soundboard-sounds.json5.example template.
2026-05-28 10:19:16 +02:00
medievalshell 7a65e5bf6d feat: soundboard (room-scoped custom audio pads)
Client side of the soundboard. Room owners enable it in Room Settings >
Misc (next to the YouTube TV toggle). When enabled, a soundboard icon
appears in the toolbar for everyone in the room; pressing a pad broadcasts
the sound so all occupants hear it. Incoming SoundboardPlay is played via
the HTML5 Audio API.

Also: fix FloorplanCanvasSVG to use ReactElement instead of the removed
global JSX namespace (React 19), and pair the client Dev branch with the
renderer fork that carries the custom features in CI.

How sounds are managed (works with any CMS):
Sounds are rows in the `soundboard_sounds` table:
    id, name, url, enabled, sort_order
The emulator loads every row with enabled=1 (ordered by sort_order, id)
and sends the list to clients on room enter; the client plays `url`
directly, so any publicly reachable audio URL works (mp3/ogg/wav).

To add a sound from an admin/housekeeping panel of any CMS:
  1. Upload the audio file to wherever the CMS stores public assets
     (same approach as custom badge images).
  2. INSERT a row into `soundboard_sounds` with the display name and the
     public URL of the uploaded file, enabled = 1.
  3. Reload the emulator soundboard (or restart) to pick it up.
Relative urls resolve against the `soundboard.url.prefix` config key
(falls back to `asset.url`); absolute urls are used as-is.
2026-05-28 09:04:17 +02:00
medievalshell 61aceaa422 feat: rare values panel + fortune wheel UI + prize editor
Toolbar buttons, FortuneWheelView (animated wheel, prize icons, recent winners),
RareValuesView (diamond price guide + staff prize-editor tab), furni infostand
value line, useFortuneWheel/useRareValues hooks, it/en text examples.
2026-05-28 02:39:02 +02:00
simoleo89 26772f7073 feat(navigator): drive search via TanStack Query + setTab/setFilter UI store
NavigatorView reads searchResult/isFetching from useNavigatorSearch
instead of useNavigatorData/useNavigatorUiState. Tab clicks call
setTab(code) on the UI store, which atomically updates the query key
and triggers refetch. The 4 lifecycle useEffect blocks driving the
old imperative flow (needsSearch / reloadCurrentSearch / markReady)
are removed — the query handles all of it now.

NavigatorSearchView has a debounced (300ms) onChange -> setFilter
that drives the same query refetch. Explicit submit (Enter / button)
skips the debounce and calls setFilter immediately.

linkTracker case 'search' now setTab + setFilter + show — no more
pendingSearch ref.

useNavigatorSearch.test.tsx: cast constructors as any to satisfy tsgo
against real renderer types while keeping runtime stubs no-arg-safe.

yarn typecheck / test / lint:hooks all clean (only pre-existing
floorplan environmental failures).
2026-05-27 19:25:30 +02:00
simoleo89 ee3736474d refactor(navigator): remove search ownership from useNavigatorStore
P2 core surgery: search result + NavigatorSearchEvent listener +
sendSearch + reloadCurrentSearch all leave useNavigatorStore. The new
useNavigatorSearch query hook owns the cache. useNavigatorActions is
deleted entirely — the only two actions it exposed are gone, and no
consumer outside Navigator depended on it.

NavigatorMetadataEvent handler now seeds the UI store's currentTabCode
on first arrival, activating the query the moment top-level contexts
land.

useNavigatorData: searchResult removed from closure and return.
useNavigatorUiState: currentTabCode + currentFilter added.
index.ts: useNavigatorActions removed, useNavigatorSearch added.

NavigatorView.tsx is intentionally broken at this commit and gets
fixed in the next.
2026-05-27 19:20:27 +02:00
simoleo89 7435326dad feat(navigator): useNavigatorSearch query hook (P2 core)
useNitroQuery keyed on [currentTabCode, currentFilter] from
navigatorUiStore. Fires NavigatorSearchComposer; subscribes to
NavigatorSearchEvent with an accept-filter that rejects results whose
code does not match the current tab. Invalidates on FlatCreatedEvent
and RoomSettingsUpdatedEvent for server-driven refresh.

nitro-renderer.mock.ts: add connection.send stub to GetCommunication
so SendMessageComposer (which calls GetCommunication().connection.send)
does not throw in tests that exercise useNitroQuery.

TDD: 7 cases incl. enabled-gating, accept-filter rejection on
mismatched tab, invalidator round-trip.
2026-05-27 19:18:24 +02:00
simoleo89 8f1b664b2f feat(navigator): add currentTabCode + currentFilter to UI store (P2 prep)
setTab(code) atomically updates currentTabCode and resets currentFilter
to '' — switching tabs starts a fresh search context. setFilter(value)
updates only the filter — the user is typing in the same tab.

TDD: 3 new cases (16 total in navigatorUiStore.test).
2026-05-27 19:15:08 +02:00
simoleo89 1148c0a628 refactor(navigator): remove deprecated useNavigator god-hook
P1 complete. All 13 consumers migrated to the wired-tools-style split:
- useNavigatorData / useNavigatorUiState / useNavigatorActions (filters)
- useNavigatorStore (internal useBetween closure with sendSearch + reloadCurrentSearch)
- navigatorUiStore (Zustand for 9 UI flags)
- useDoorState (extracted to src/hooks/rooms/widgets)

Spec: docs/superpowers/specs/2026-05-26-navigator-modernization-p1-design.md
Plan: docs/superpowers/plans/2026-05-26-navigator-modernization-p1.md

Next phases (separate specs/plans): P2 (TanStack Query for search),
P3 (reactive favourites via snapshot), P4 (visual rework + virtualization
+ persistence).
2026-05-27 19:01:48 +02:00
simoleo89 3c10ccdaee fix(navigator): restore useNotification() inside useNavigatorStore
Commit 8ab0021a introduced an unjustified deviation: it removed the
useNotification() call from inside useNavigatorStore and replaced it
with a module-level _simpleAlert ref + _injectSimpleAlert() exported
function, on the theory that nested useBetween calls corrupt
use-between's state.

That diagnosis is wrong. Production proof:
- useCatalog.ts:56 calls useNotification() inside useCatalogStore
- useWiredToolsStore.ts:131 calls useNotification() inside its store
- The original useNavigator.ts:32 calls useNotification() inside its
  state closure
All three have been in production for ages without issue. Nested
useBetween calls work fine.

The smoke-test failure that prompted the workaround was a mock issue,
not a real bug. Reverting to the standard pattern — useNotification()
direct inside the useBetween store closure. Production alerts work
again immediately without requiring an explicit injection call from
consumers.

Mock additions (src/nitro-renderer.mock.ts):
- Added 23 notification MessageEvent subclasses (AchievementNotification-
  MessageEvent, ActivityPoint..., BadgeReceived, ClubGiftNotification,
  ClubGiftSelected, ConnectionError, HabboBroadcast, HotelClosedAndOpens,
  HotelClosesAndWillOpenAt, HotelWillCloseInMinutes, InfoFeedEnable,
  MaintenanceStatus, ModeratorCaution, ModeratorMessage, MOTD,
  NotificationDialog, PetLevel, PetReceived, RespectReceived, RoomEnter,
  SimpleAlert, UserBanned, WiredRewardResult) so useNotificationStore
  can register its listeners without throwing.
- Added RoomEnterEffect stub (isRunning: false, totalRunningTime: 0).
- Added WiredRewardResultMessageEvent static constants.
2026-05-27 18:50:40 +02:00
simoleo89 8ab0021af6 feat(navigator): wired-tools-style hook split (Store + 3 filters)
Splits the 492-line useNavigator god-hook into a useBetween-backed
useNavigatorStore closure plus three flat-shape filters
(useNavigatorData, useNavigatorUiState, useNavigatorActions), mirroring
the wired-tools layout. sendSearch + reloadCurrentSearch are extracted
as named actions out of NavigatorView locals.

Door-mode handling is removed from this store and lives in useDoorState
(committed previously) - see GetGuestRoomResultEvent and
GenericErrorEvent dual-subscription with mutually exclusive filters.

The simpleAlert dependency is lifted out of the useBetween scope via a
module-level _simpleAlert ref + _injectSimpleAlert() to avoid nested
useBetween calls that corrupt use-between's module-level dispatcher
state. The ref is null in tests (no events fire during smoke tests) and
is populated in production by the navigator consumer before any alert
is needed.

The barrel index.ts no longer re-exports useNavigator. The 13 consumers
will fail typecheck until the next commit migrates them; the hook files
themselves are clean. Smoke test covers filter shapes.

INTENTIONAL INTERMEDIATE-BROKEN COMMIT: yarn typecheck is RED at this
SHA on the 13 consumer files. The next commit (consumer migration sweep)
brings it back to green.
2026-05-27 18:44:24 +02:00
Life fac2878bc8 Merge branch 'duckietm:main' into feat/navigator-modernization 2026-05-27 18:32:42 +02:00
duckietm a52a4a024a 🆕 Added Pickup furni to the floorplan 2026-05-27 09:39:08 +02:00
duckietm acf870ff6a 🆙 Enable back the live previes of the floorplan 2026-05-27 07:46:10 +02:00