mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 06:56:20 +00:00
docs: comprehensive refresh after the React 19 modernization round
Three top-level files brought in sync with the work landed on feat/react19-modernization: - CHANGELOG.md gets a 'React 19 Modernization Phase 2 (2026-05-12)' section spanning all four pattern groups (event-state companions, TanStack queries on the catalog layer, god-hook splits in the doorbell + singleton-filter styles, Pixi v8 / TS 5.7+ alignment), the Vitest growth 65 -> 113, and the in-scope logic bug fixes. - ARCHITECTURE.md bumps the test ledger 99 -> 113 (adds the avatar-info reducer suite), documents the new pure-module test convention (concrete file paths + 'import type' for renderer event types), and lists the two new singleton-filter splits (notification, friends). - CLAUDE.md mirrors the same updates plus a 'Singleton-filter split' recipe alongside the doorbell-style one; useNitroEventInvalidator is documented next to useNitroQuery; the 'What's wired up' table enumerates all 10 split hooks. Test count bumped 99 -> 113 in both the 'Vitest' row and the green-bar house rule.
This commit is contained in:
+121
@@ -1,5 +1,126 @@
|
||||
# Changelog
|
||||
|
||||
## React 19 Modernization Phase 2 (2026-05-12)
|
||||
|
||||
Long-running work on the `feat/react19-modernization` branch — see
|
||||
[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for the design rationale.
|
||||
Companion changes shipped on `feat/react19-event-bus` in
|
||||
[`Nitro_Render_V3`](../Nitro_Render_V3) — see that repo's CLAUDE.md
|
||||
for the renderer-side notes.
|
||||
|
||||
### Pattern #1: `useNitroEventState` + companions
|
||||
- New `useNitroEventReducer` / `useMessageEventReducer` for the case
|
||||
where multiple event types collapse into one owned state slice.
|
||||
- New `useExternalSnapshot` — typed wrapper of
|
||||
`useSyncExternalStore` pairing the renderer's
|
||||
`EventDispatcher.subscribe()` with `getXxxSnapshot()` getters.
|
||||
- Pilot adoption: `useAvatarInfoWidget` now owns the figure / badges /
|
||||
group merge (three event listeners moved out of
|
||||
`InfoStandWidgetUserView`, three `CloneObject` calls dropped).
|
||||
Reducers extracted to `src/hooks/rooms/widgets/avatarInfo.reducers.ts`
|
||||
with 14 Vitest cases.
|
||||
- `useInventoryFurni` refactored to call three pure reducers
|
||||
(`useInventoryFurni.reducers.ts`) instead of inlining ~250 LOC of
|
||||
merge logic in the event handlers. Module-level
|
||||
`furniMsgFragments` becomes a `useRef` — eliminates a latent bug
|
||||
where two simultaneous client instances would have trampled each
|
||||
other's fragment buffers. Empty `FurniturePostItPlacedEvent` listener
|
||||
dropped.
|
||||
|
||||
### Pattern #2: `useNitroQuery` adoption
|
||||
- New `useNitroEventInvalidator(eventType, queryKey, accept?)` companion
|
||||
in `src/api/nitro-query/` — invalidates a query slot every time the
|
||||
renderer pushes the matching parser event. Required when the server
|
||||
refreshes data outside the request cycle (e.g. ClubGiftInfoEvent
|
||||
after a gift claim).
|
||||
- Seven catalog fetches lifted out of `useCatalog` into dedicated
|
||||
TanStack queries:
|
||||
- `useGiftConfiguration` (GiftWrappingConfigurationEvent)
|
||||
- `useUserGroups` — consolidates 5 sites that each dispatched
|
||||
`CatalogGroupsComposer` independently
|
||||
- `useClubOffers(windowId)` — per-windowId, with `accept` filter
|
||||
- `useSellablePetPalette(breed)` — per-breed, with `accept` filter
|
||||
- `useMarketplaceConfiguration` — lifted out of a self-fetch in
|
||||
`MarketplacePostOfferView`
|
||||
- `useClubGifts` — paired with `useNitroEventInvalidator` for the
|
||||
server-push-after-SelectClubGift case
|
||||
- `ICatalogOptions` (the "catalogOptions" bag that the various views
|
||||
stuffed their fetched data into) is now **empty and deleted**.
|
||||
|
||||
### Pattern #4: god-hook splits
|
||||
Five new splits in this round, two patterns. The doorbell-style
|
||||
(state + actions + shim, no shared singleton) for hooks whose actions
|
||||
are pure-dispatch:
|
||||
|
||||
- **chat-input** (334 LOC → 3 files) — `useChatInputState` owns the
|
||||
5 state slices + 3 event listeners + 3 lifecycle effects;
|
||||
`useChatInputActions` owns `sendChat` with the full slash-command
|
||||
repertoire and the outgoing-translation pipeline. Single consumer
|
||||
(`ChatInputView`) keeps the original tuple via the shim.
|
||||
|
||||
The `useBetween` singleton-filter style for hooks where actions
|
||||
mutate shared state:
|
||||
|
||||
- **wired-tools** (618 LOC) — 20 consumers; `useWiredToolsStore`
|
||||
internal singleton, public `useWiredToolsState` /
|
||||
`useWiredToolsActions` filter views, `useWiredTools` shim.
|
||||
- **translation** (600 LOC) — 6 consumers; `useTranslationStore`
|
||||
inline + filter views.
|
||||
- **notification** (493 LOC) — ~44 consumers, most of which use a
|
||||
single action (`simpleAlert` or `showConfirm`); the read-only state
|
||||
slice exposes the three queue arrays for the renderer view layer.
|
||||
- **friends** (258 LOC) — 16 consumers; state slice covers the friend
|
||||
list / settings / derived online-offline split, actions slice covers
|
||||
`requestFriend` / `requestResponse` / `followFriend` /
|
||||
`updateRelationship`.
|
||||
|
||||
Documented skip-motivated splits: `useChatWidget`,
|
||||
`useChatCommandSelector`, `useFurniturePresentWidget`,
|
||||
`useAvatarInfoWidget`, `useNavigator`, `useMessenger`,
|
||||
`usePetPackageWidget`, `useWordQuizWidget`. Reasons logged in commit
|
||||
messages.
|
||||
|
||||
### Typecheck / Pixi v8 / Arcturus alignment
|
||||
- Repository-wide `tsgo` (TS 7 preview) error count: **134 → 0** client,
|
||||
**24 → 0** renderer. Notable clusters: framer-motion `Variants`
|
||||
typing on Toolbar + FriendsBar (-33), `useFurniChooserState`
|
||||
retyped as `IRoomObject` + dead `getUserData` guard dropped (-10),
|
||||
React 19 `useRef<T>()` → `useRef<T>(null)` sweep on 15 sites (-15),
|
||||
`IGetImageListener` single-arg signature migration on 3 sites,
|
||||
`ColorVariantType` extended with the 5 `outline-*` bootstrap
|
||||
variants.
|
||||
- Renderer-side aligned with Pixi v8 (Filter[] narrowing,
|
||||
WebGLRenderer narrowing, ImageLike cast) and TS 5.7+ ArrayBuffer
|
||||
drift (BinaryReader / BinaryWriter / WsSessionCrypto / NitroBundle).
|
||||
- Cross-repo additions on `Nitro_Render_V3`:
|
||||
`RoomEnterComposer` now accepts optional `spawnX`/`spawnY` matching
|
||||
Arcturus' `RequestRoomLoadEvent` optional tail; `RoomSettingsData`
|
||||
surfaces the `allowUnderpass` field that Arcturus already emits.
|
||||
Dead `sendWhisperGroupMessage` / `ChatWhisperGroupComposer`
|
||||
reference removed.
|
||||
|
||||
### Vitest coverage
|
||||
Bumped from 65 → 113 cases across 8 test files. New coverage:
|
||||
- `dedupeBadges.test.ts` (6) — slot-preserving badge dedup.
|
||||
- `catalog-favorites.helpers.test.ts` (16) — v2→v3 localStorage
|
||||
migration + per-catalog-type storage-key routing.
|
||||
- `avatar-info-reducers.test.ts` (14) — three reducer bail-out
|
||||
branches + apply paths.
|
||||
- `friendly-time.test.ts` (12) — `FriendlyTime` with a deterministic
|
||||
`LocalizeText` mock.
|
||||
|
||||
### Logic bug fixes (in scope)
|
||||
- `useInventoryFurni`'s module-level `furniMsgFragments` buffer
|
||||
scoped to `useRef`.
|
||||
- `RoomChatHandler.dispatchEvent(RoomSessionChatEvent)` arg order
|
||||
fix in renderer — `chatColours` and `links` slots were swapped.
|
||||
- `PetBreedingMessageParser.bytesAvailable < 12` was a boolean-vs-number
|
||||
bug; replaced with the standard guard pattern.
|
||||
- `useOnClickChat` was passing an extra 8th arg to `showConfirm`
|
||||
(signature only takes 7).
|
||||
- `UserContainerView` was passing `userProfile.friendsCount` (number)
|
||||
to a `LocalizeText` placeholder array (expects string).
|
||||
|
||||
## Badge System Rework (2026-04-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -109,8 +109,46 @@ const { data } = useNitroQuery<SomeParser, SomeData>({
|
||||
```
|
||||
|
||||
Already wired up; `QueryClientProvider` is mounted in `src/index.tsx`.
|
||||
Adopted on `OfferView`, `CatalogLayoutRoomAdsView`, `ModToolsChatlogView`,
|
||||
`CfhChatlogView`.
|
||||
|
||||
Companion `useNitroEventInvalidator(eventType, queryKey, accept?)` —
|
||||
import from `src/api/nitro-query`. Subscribes to the renderer event
|
||||
and invalidates the query slot on every push, so server-driven
|
||||
refresh paths work the same as the initial request/response (e.g.
|
||||
ClubGiftInfoEvent firing again after the user claims a gift).
|
||||
|
||||
### Singleton-filter split for `useBetween`-based hooks
|
||||
|
||||
When a hook backs many consumers but most only need either state OR
|
||||
actions (not both), split it without breaking the shared-singleton
|
||||
guarantee:
|
||||
|
||||
```ts
|
||||
// internal: state + actions in one closure
|
||||
const useFooStore = () => {
|
||||
const [ data, setData ] = useState(...);
|
||||
// listeners, effects, actions ...
|
||||
return { data, doThing };
|
||||
};
|
||||
|
||||
// public: read-only filter
|
||||
export const useFooState = () => {
|
||||
const { data } = useBetween(useFooStore);
|
||||
return { data };
|
||||
};
|
||||
|
||||
// public: imperative filter
|
||||
export const useFooActions = () => {
|
||||
const { doThing } = useBetween(useFooStore);
|
||||
return { doThing };
|
||||
};
|
||||
|
||||
// deprecated shim — keeps the historical return shape
|
||||
export const useFoo = () => useBetween(useFooStore);
|
||||
```
|
||||
|
||||
`useBetween` ensures all three entry points hit the same store
|
||||
instance, so listeners/effects register once. Used by `useWiredTools`,
|
||||
`useTranslation`, `useNotification`, `useFriends`.
|
||||
|
||||
### Zustand stores
|
||||
|
||||
@@ -159,9 +197,9 @@ Login / Register / Forgot in `src/components/login/LoginView.tsx` use
|
||||
| `useNitroQuery` + `useNitroEventInvalidator` | `OfferView`, `CatalogLayoutRoomAdsView`, `ModToolsChatlogView`, `CfhChatlogView`, `useGiftConfiguration`, `useUserGroups`, `useClubOffers(windowId)`, `useSellablePetPalette(breed)`, `useMarketplaceConfiguration`, `useClubGifts` (with invalidator) |
|
||||
| Zustand | `NavigatorRoomCreatorView` (`useRoomCreatorStore`) |
|
||||
| God-hook split (state + actions + shim) | `doorbell`, `poll`, `furni-chooser`, `user-chooser`, `friend-request`, `chat-input` |
|
||||
| God-hook split (`useBetween` singleton + state filter + actions filter + shim) | `wired-tools`, `translation` |
|
||||
| God-hook split (`useBetween` singleton + state filter + actions filter + shim) | `wired-tools`, `translation`, `notification`, `friends` |
|
||||
| `WidgetErrorBoundary` | `RoomWidgetsView` umbrella |
|
||||
| Vitest | 99/99 cases on pure helpers + the Zustand store |
|
||||
| Vitest | 113/113 cases on pure helpers + the Zustand store |
|
||||
|
||||
| Not yet | Notes |
|
||||
|---|---|
|
||||
@@ -197,7 +235,7 @@ Fix shapes documented; both are reasonable PRs on their own.
|
||||
- **Skip-motivated god-hook splits are fine** — when a hook's actions
|
||||
mutate internal state, document the reason in the commit message and
|
||||
move on rather than forcing a bad split.
|
||||
- **`yarn test` must stay green** on every commit. Currently 99/99.
|
||||
- **`yarn test` must stay green** on every commit. Currently 113/113.
|
||||
- **Lint baseline**: don't regress. Some pre-existing errors (`FC<{}>`,
|
||||
`IMessageEvent | undefined` redundant union in the local sandbox where
|
||||
the renderer SDK isn't installed) are out of scope here.
|
||||
|
||||
+20
-2
@@ -427,6 +427,18 @@ The current branch (**`feat/react19-modernization`**, PR #2) has applied:
|
||||
Wired tools — six consumers split across read-only views
|
||||
(settings panel, bootstrap) and dispatch sites (messenger, chat
|
||||
input).
|
||||
- **notification**: `useNotificationStore` (internal singleton) +
|
||||
`useNotificationState` (queue arrays for the renderer view) +
|
||||
`useNotificationActions` (8 entry points: simpleAlert,
|
||||
showNitroAlert, showTradeAlert, showConfirm, showSingleBubble,
|
||||
closeAlert, closeBubbleAlert, closeConfirm) + shim. The ~30
|
||||
message-event listeners and 5 state slices stay in the singleton.
|
||||
Used by ~44 consumers, most of which only need one action.
|
||||
- **friends**: `useFriendsStore` (internal singleton) +
|
||||
`useFriendsState` (friends arrays, settings, derived
|
||||
online/offline split, lookup helpers) + `useFriendsActions`
|
||||
(requestFriend, requestResponse, followFriend, updateRelationship)
|
||||
+ shim. 16 consumers.
|
||||
- **Zustand** (proposal #5) — **enabled**. `zustand` installed; factory at
|
||||
`src/state/createNitroStore.ts`. First adoption: the `let isCreatingRoom`
|
||||
/ `createRoomTimeout` module-level pair in `NavigatorRoomCreatorView`
|
||||
@@ -472,7 +484,7 @@ Status after this round of work:
|
||||
- Vitest 3 + jsdom + `@testing-library/react` + `@testing-library/jest-dom`
|
||||
configured. Separate `vitest.config.mts` so the runner doesn't drag in
|
||||
the renderer SDK aliases from `vite.config.mjs`.
|
||||
- **99 cases passing** across 7 test files:
|
||||
- **113 cases passing** across 8 test files:
|
||||
- `WiredCreatorTools.helpers.test.ts` (18) — formatters + snapshot
|
||||
factory.
|
||||
- `navigatorRoomCreatorStore.test.ts` (4) — Zustand store invariants
|
||||
@@ -488,10 +500,16 @@ Status after this round of work:
|
||||
(covers the helper used by the InfoStand pilot).
|
||||
- `catalog-favorites.helpers.test.ts` (16) — localStorage parse +
|
||||
v2→v3 migration + per-catalog-type storage-key routing.
|
||||
- `avatar-info-reducers.test.ts` (14) — InfoStand reducer pilot:
|
||||
bail-out branches (state-not-AvatarInfoUser, mismatched
|
||||
user/roomIndex, equal-after-dedup) + the figure / favorite-group
|
||||
apply paths.
|
||||
- **Pure-module convention**: tests live in `tests/` and import from
|
||||
concrete file paths (e.g. `../src/api/catalog/CatalogType`) rather
|
||||
than the api barrel, so jsdom doesn't transitively load the renderer
|
||||
SDK's Pixi-bound modules.
|
||||
SDK's Pixi-bound modules. Renderer event type imports use
|
||||
`import type { … }` so they're erased at compile time and don't
|
||||
trigger the runtime module load either.
|
||||
- `yarn test` + `yarn test:watch` scripts added.
|
||||
|
||||
### Logic bug fixes
|
||||
|
||||
Reference in New Issue
Block a user