mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 23:16:21 +00:00
88d212829553cc8512e59f34d21d19a3de52e671
21 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3459400ed7 |
docs(claude,architecture): refresh snapshot adoption status after 2026-05-19 fix
The earlier "BLOCKED" / "rolled back" framing in CLAUDE.md +
ARCHITECTURE.md is stale: the three pilot snapshot-consumer migrations
shipped in
|
||
|
|
f4ada81321 |
docs(CLAUDE.md,ARCHITECTURE.md): record snapshot-consumer rollback (e142efd)
CLAUDE.md updates:
- Patterns to use: "useSessionSnapshots" section retitled "(OPT-IN)";
the documented pilot adopters (useSessionInfo,
AvatarInfoWidgetAvatarView) are removed. Adds explicit warning about
the suspected useBetween + useSyncExternalStore + React Compiler
interaction and the rollback in
|
||
|
|
803de20dfe |
tests: flatten renderer mock to src/nitro-renderer.mock.ts (drop __mocks__/)
The Jest-style __mocks__/ folder added one indirection for a single file. Move the stub to src/nitro-renderer.mock.ts at src/ root next to test-setup.ts, drop the folder, repoint the vitest alias, and update the lone test that imports the helpers directly (useDoorbellState). Same behaviour, one fewer directory. |
||
|
|
8b4308af16 |
tests: co-locate every Vitest suite next to its subject under src/
Eliminate the parallel `tests/` tree. Each `*.test.ts` / `*.test.tsx` now sits in the same directory as the module it covers, mirroring its filename (`Foo.ts` ↔ `Foo.test.ts`). The renderer-SDK mock used by component / hook tests moves to `src/__mocks__/nitro-renderer.ts` and the Vitest setup file becomes `src/test-setup.ts` — both still wired through `vitest.config.mts` exactly as before, only the paths changed. All 13 suites + 178/178 cases still pass. The production build is unaffected: rollup only follows imports from `src/index.tsx` and never crosses into `.test.ts` files, so test code is naturally tree-shaken out of the bundle. `yarn build` output is byte-for-byte the same on the user-facing chunks. tsconfig drops the now-redundant `tests` include entry. CLAUDE.md 'Layout convention' replaces the old `tests/` row with three rows documenting the new co-located convention, the `__mocks__/` directory and the `test-setup.ts` entry; ARCHITECTURE.md picks up the same update. The 'DO NOT CHANGE' qualifier on the layout is preserved — this rewrite IS the change, decided deliberately to make tests a first-class part of the source tree rather than a sibling project. |
||
|
|
ab93113ce7 |
widgets: wrap each room + furniture widget in its own WidgetErrorBoundary
The umbrella boundary on RoomWidgetsView caught any widget crash but unmounted every sibling along with the failing widget — a single bad parser in ChatWidget would dark out the avatar info, chat input, doorbell and all furniture overlays until the next remount. Wraps each of the 13 direct children of RoomWidgetsView (AvatarInfo, Chat, ChatInput, Doorbell, RoomTools, RoomFilterWords, RoomThumbnail, FurniChooser, PetPackage, UserChooser, WordQuiz, FriendRequest, plus the FurnitureWidgets umbrella) and each of the 20 sub-widgets inside FurnitureWidgetsView in its own named WidgetErrorBoundary. A crash now silently logs through NitroLogger with the widget name and renders null for that one widget; every sibling keeps rendering. The outer umbrella stays as defense-in-depth for the wrapper div and the listener setup in RoomWidgetsView itself. Closes the "Per-widget WidgetErrorBoundary wrapping" roadmap item; updates CLAUDE.md and docs/ARCHITECTURE.md accordingly. |
||
|
|
97c9717253 |
fix(layout-image): guard async image fetch with a request-id ref
LayoutFurniImageView and LayoutAvatarImageView both fired async image generation (TextureUtils.generateImage / SDK resetFigure callback) and wrote the result back through setImageElement / setAvatarUrl with only an isMounted / isDisposed component-level guard. If props changed twice in rapid succession the older request could resolve last and overwrite the newer image with a stale one, visible on slow connections or fast scroll over grids of unique items. Each effect now captures `const requestId = ++requestIdRef.current` and threads it into every async callback (TextureUtils.generateImage, the SDK's resetFigure listener, the cache write). When a callback fires it bails if `requestIdRef.current !== requestId` — only the latest effect's callbacks make it past the gate. A stale ENDED for the previous figure now leaves the cache and the rendered url unchanged. Moves both bugs from "Open" to "Recently fixed" in docs/ARCHITECTURE.md. |
||
|
|
9d10e52a55 |
fix(MainView): collapse CREATED/ENDED listeners into a session-aware reducer
Two independent useNitroEvent listeners updated landingViewVisible from RoomSessionEvent.CREATED and ENDED with no notion of which session was active. Under flaky websocket reconnects the events can land out of order: a stale ENDED for the previous room arrives after CREATED for the new one, flips landingViewVisible back to true, and the user is left at the hotel view inside a room (or vice versa) until the next room change. Folds both events into one useNitroEventReducer that carries the tracked sessionId. CREATED sets the id and closes the landing view; ENDED is applied only when its event.session.roomId matches the tracked id (or no session is active) — otherwise it's a stale ENDED for a previous session and is ignored. The reducer companion is the existing useNitroEventReducer from src/hooks/events, so no new infrastructure. Moves the entry in docs/ARCHITECTURE.md from "Open" to "Recently fixed". |
||
|
|
0f9fa1203b |
catalog: migrate remaining 36 useCatalog() consumers to the three filters
Replaces every direct call to the deprecated useCatalog() shim with the targeted filter(s) (useCatalogData / useCatalogUiState / useCatalogActions). Each consumer now subscribes only to the slice it actually reads, which restores React Compiler memoization and stops catalog-wide re-renders whenever an unrelated key changes. Removes the now-unused useCatalog shim from useCatalog.ts and the shim-specific case in tests/useCatalog.filters.test.tsx. The "all four hooks observe the same singleton" test becomes "all three filters", since there is no shim left to compare against. useCatalogFavorites swaps its internal useCatalog() call for useCatalogUiState() (currentType lives in the UI slice). Updates CLAUDE.md and docs/ARCHITECTURE.md to reflect that all 48 historical consumers are migrated and the shim is gone. Vitest: 162/162 (was 163 — minus the deprecated-shim contract case). |
||
|
|
59d6c4cab3 |
catalog: three-way singleton-filter split + first 3 consumer migrations
Completes the useCatalog decomposition. After the previous commit
extracted the pure helpers, this one splits the singleton-via-useBetween
store into three slice-specific entry points and migrates a handful of
consumers as proof.
`src/hooks/catalog/useCatalog.ts`
- Internal `useCatalogState` → renamed to `useCatalogStore` and is no
longer exported. The full return shape is unchanged so callers that
still go through the shim see the exact same object.
- Three new exports built on top of the same `useBetween` instance:
- `useCatalogData()` — server-driven read-only slice (rootNode,
offersToNodes, currentPage, currentOffer, frontPageItems,
searchResult, roomPreviewer, isBusy, catalog localization
version, Builders Club counters + timers).
- `useCatalogUiState()` — UI ephemeral state + writers
(isVisible, pageId, previousPageId, currentType, activeNodes,
navigationHidden, purchaseOptions, catalogPlaceMultipleObjects,
plus every `set*` writer including the ones that mutate the
data slice on user-driven selection).
- `useCatalogActions()` — imperative operations only
(openCatalogByType, toggleCatalogByType, activateNode,
openPageBy{Id,Name,OfferId}, requestOfferToMover,
selectCatalogOffer, getNodeBy{Id,Name},
getBuilderFurniPlaceableStatus).
- `useCatalog` is kept as a deprecated shim that returns the full
historical surface, so the 48 existing consumers compile and run
unchanged.
Pilot consumer migrations (3 of 48):
- `CatalogBuildersClubStatusView` — Data (furni counters, seconds
timers) + UiState (currentType).
- `CatalogBreadcrumbView` — UiState (activeNodes) + Actions
(activateNode).
- `CatalogNavigationItemView` — UiState (currentType) + Actions
(activateNode).
Tests: `tests/useCatalog.filters.test.tsx` (5 cases).
`useBetween` is mocked via `vi.hoisted` so the four hooks share one
deterministic fake store — rendering the real `useCatalogStore`
would mount ~30 useState calls + open a fresh RoomPreviewer +
subscribe to a dozen renderer events, which is more than these
contract tests need.
- `useCatalogData` exposes exactly its read-only keys.
- `useCatalogUiState` exposes exactly its UI keys + setters.
- `useCatalogActions` exposes exactly its imperative ops (and
explicitly NOT data fields — proves no leak across slices).
- Singleton identity: callbacks read through the shim are `===` to
the ones read through the slices.
- Shim surface: the historical key set is still present so
un-migrated consumers don't silently break.
Suite: 163/163 (was 158/158). `yarn typecheck` green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
fd3ef7875d |
catalog: extract pure helpers + 34 cases, consume them from useCatalog
First half of the proposed `useCatalog` decomposition. The 1036-line
god-hook still owns the singleton-via-useBetween, but the pure logic
it used to define inline now lives in a dependency-free module so it
can be tested in isolation and reused by future split-out hooks
(`useCatalogData` / `useCatalogUiState` / `useCatalogActions` when
those land).
New module: `src/hooks/catalog/useCatalog.helpers.ts` (222 LOC).
- `normalizeCatalogType(type?)` — coerce the optional catalog type to
`NORMAL` / `BUILDER`. Was a 5-line `useCallback` with an empty
dependency array.
- `getOfferProductKeys(offer)` — produces the canonical
`productType:id:classId` and `productType:class:className` keys
for the resolved-offer cache.
- `findNodeById` / `findNodeByName` — DFS over the catalog tree,
root explicitly excluded so callers can't select the synthetic
root by mistake.
- `getNodesByOfferIdFromMap(offerId, map, onlyVisible)` — extracted
from the closed-over `getNodesByOfferId`. The `onlyVisible`
fallback to the full bucket when nothing visible remains is
preserved.
- `buildCatalogNodeTree(NodeData)` — pulled out of the
`CatalogPagesListEvent` reducer. Builds the tree and the offerId
index in one pass; the caller now does `const { rootNode,
offersToNodes } = buildCatalogNodeTree(parser.root)` instead of
carrying an inline recursive walker + a local map.
- `resolveBuilderFurniPlaceableStatus(input)` — the placement
decision tree as a pure function. The hook keeps the
`GetRoomEngine` / `GetSessionDataManager` reads that count
non-self, non-moderator visitors (only when the subscription has
expired) and forwards the resulting `visitorCount` into the
helper, so the previous early-exit semantics are preserved.
`useCatalog.ts` now imports these and removes ~140 lines of inline
copies. Net hook size: 1036 → 961 LOC. Behavior unchanged.
Tests: `tests/useCatalog.helpers.test.ts` (34 cases).
- `normalizeCatalogType` (4) — BUILDER pass-through, NORMAL
pass-through, undefined/empty fallback, unknown string fallback.
- `getOfferProductKeys` (5) — both keys, id-only when classId<0,
class-only when className empty, no-product short-circuit,
empty productType short-circuit.
- `findNodeById` (5) — null input, root exclusion, immediate child,
grandchild, miss returns null.
- `findNodeByName` (2) — match by name + root exclusion, miss.
- `getNodesByOfferIdFromMap` (5) — empty map, raw bucket pass-through,
visible-only filter, fallback when no visible remain, miss.
- `buildCatalogNodeTree` (3) — root depth=0 + empty offer map for a
leaf-only root, DFS traversal tracks offer→nodes across branch
and leaf, child.parent === root.
- `resolveBuilderFurniPlaceableStatus` (10) — missing offer,
not-in-room, owner happy path, non-owner without fallback,
guild admin with time, furni limit reached, shared-pool override
ignoring the limit, expired+blocked-by-visitors flag,
expired+visitor count > 0, expired+empty room is okay.
To support the placement-status test the renderer mock gains real
numeric values for `RoomControllerLevel` (NONE..MODERATOR) and
`RoomObjectCategory` (MINIMUM..MAXIMUM); the previous string-keyed
Proxy stubs made `controllerLevel >= GUILD_ADMIN` evaluate to NaN.
Suite: 158/158 (was 124/124). `yarn typecheck` green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
c4018392f9 |
tests: add renderer-SDK mock layer + first 2 component-/hook-level pilots
Foundations for widening Vitest coverage past the pure-helper subset.
The real `@nitrots/nitro-renderer` eagerly loads Pixi v8 and the full
Habbo message parser/composer registry at module-import time, which
jsdom cannot host: any `tests/**` file that transitively pulled a
renderer symbol would throw before a single assertion ran. That's
why the existing 8 suites all stuck to pure modules imported by
concrete path and used `import type` for renderer-side names.
Add a stub at `tests/mocks/renderer-mock.ts`, aliased over the package
via `vitest.config.mts`. It exports:
- Explicit behavioral stubs for the symbols tests actually exercise:
`NitroLogger`, `GetEventDispatcher`, the `mockEventDispatcher`
helper with `addEventListener` / `removeEventListener` /
`dispatchEvent` / `hasListeners`, and `RoomSessionDoorbellEvent`
(signature matches the real `(type, session, userName)` to keep
tsgo happy).
- String-keyed Proxy enums for `NitroEventType`, `RoomObjectCategory`,
`AvatarFigurePartType`, etc. — each access returns a stable unique
string so dispatch and listener agree.
- Lightweight `class StubClass {}` placeholders for the ~30 Pixi and
gameplay classes the `src/api/*` barrel touches at import time
(`NitroAlphaFilter`, `NitroContainer`, `EventDispatcher`, …).
Keeps the cascade from throwing without simulating behavior tests
don't care about.
- Singleton getters (`GetAssetManager`, `GetCommunication`,
`GetSessionDataManager`, …) returning a chainable Proxy so deeply
nested `GetX().y.z(…)` access evaluates to no-op proxies.
Pilots on top of that layer (each one designed to catch a different
class of regression):
- `tests/WidgetErrorBoundary.test.tsx` (4 cases) — happy path,
default silent fallback + `NitroLogger.error` call, custom
fallback node, default `unknown` widget name.
- `tests/useDoorbellState.test.tsx` (7 cases) — initial empty state,
append on `RSDE_DOORBELL`, dedup duplicate names, remove on
`RSDE_ACCEPTED` / `RSDE_REJECTED`, ignore stale events for
never-pending users, full unsubscribe on unmount.
Suite count now 124/124 across 10 files (was 113/113 across 8).
`yarn typecheck` still green.
Docs: CLAUDE.md's Vitest row and "Where everything lives" pointer
updated; `docs/ARCHITECTURE.md` Tests section now lists the new
suites + a description of what the mock layer covers, and the
"Wider Vitest coverage" entry in the next-steps list is reframed
from "needs a renderer mock" to "pick the next adopter".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
622d73c2f0 |
docs: reflect PR #126 cherry-pick + boot/asset infrastructure
CLAUDE.md - TL;DR mentions the duckietm PR #126 cherry-pick (UserAccountSettings, wear-badge popup fix) and the sirv-based dev asset serving so a fresh session knows what's living on top of upstream main. - New patterns section for the bootstrap.ts configuration pre-init and the nitroAssetsServer Vite plugin, with a pointer to the .gitignore note explaining why public/{nitro-assets,swf} symlinks are a trap. - "What's wired up" table gets two rows: Form Actions, and the PR #126 pickup. - "Where everything lives" gets entries for UserAccountSettingsView, the persistAccessTokenFromPayload helper, the asset middleware, and the bootstrap pre-init call. docs/ARCHITECTURE.md - Recently fixed: adds the useAvatarEditor PETS/MISC paletteID null-pointer that surfaced when the editor was opened. - New Bonus subsections describing the boot-time orchestration in bootstrap.ts, the dev asset serving via sirv (and why symlinking under public/ is the wrong move on Windows), and the upstream feature catch-up via PR #126. No code changes in this commit — pure documentation refresh. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
cc225bdc5d |
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. |
||
|
|
7cf01b0947 |
docs: refresh ARCHITECTURE + CLAUDE with this session's work
- Pattern #2 (useNitroQuery): list the eight catalog-layer queries carved out of useCatalog this session (gift / groups / club offers / pet palette / marketplace / club gifts), plus the new useNitroEventInvalidator companion for server-push refresh. - Note ICatalogOptions deleted — the legacy 'catalogOptions' bag has no remaining fields after the migrations, useCatalog no longer exposes it. - New 'useCatalog decomposition (in progress)' table — what's been lifted to TanStack and what's deferred (page tree + Builders Club status, both core state slices that move with the data/actions split). - Pattern #4 (god-hook split): add the three new splits done this session (chat-input doorbell-style, wired-tools singleton-filter, translation singleton-filter inline). - Bump Vitest count 83 → 99 (added 16 cases on the useCatalogFavorites helpers). - Note the pure-module test convention: import from concrete file paths rather than the api barrel to avoid jsdom pulling in Pixi. - Typecheck baseline: client now reports 0 too (was 57 at last doc-write); the section's enumerated sweeps all landed. - CLAUDE.md: bump '77/77' references to '99/99' (both places). |
||
|
|
eeb9cc66a5 |
Split useTranslation into state + actions via useBetween singleton
Same pattern as the wired-tools split: 600-line useTranslation backs 6 consumers with a wide state + action surface. Split along the read/write seam: - useTranslationStore (internal, was the inner useTranslationState) — the previous singleton body, untouched except for the rename and a doc-comment. - useTranslationState (public, read-only) — useBetween filter exposing settings, the supported-languages list, the loading/loaded flags, the detected-language tags, lastError, and the pure getLanguageName helper. - useTranslationActions (public, imperative) — same singleton filter exposing updateSettings, ensureSupportedLanguagesLoaded, the four translate/queue helpers. Also re-exposes 'settings' because most call sites need 'if(settings.enabled)' before dispatching. - useTranslation (deprecated shim) — composes the singleton via useBetween, preserving the historical full-shape return. applyTextTranslationLocale stays exported from the same module path so LoginView's import keeps working. Updates docs/ARCHITECTURE.md proposal #4 section to list the three new splits (chat-input + wired-tools + translation) alongside the previous five. |
||
|
|
f1af6fb68a |
docs: ARCHITECTURE pattern #1 — companions implemented, pilots adopted
Updates the proposal #1 section to reflect the four companion hooks now in src/hooks/events/ (useNitroEventReducer, useMessageEventReducer, useExternalSnapshot, on top of the existing *State hooks) and marks the InfoStand + Inventory pilots from the original Fase 2 plan as adopted. Adds the convention note for state owned outside the listener: keep useState + useMessageEvent and extract the reducer as a pure function, citing the two new reducer modules as reference. |
||
|
|
f75762a2db |
Add CLAUDE.md + refresh docs/ARCHITECTURE.md to current state
Two doc changes so a fresh local Claude Code session can pick up the
branch without re-discovering the conventions and the work-in-progress.
CLAUDE.md (new, repo root)
- Onboarding file Claude Code reads automatically at session start.
- TL;DR with branch name + PR number, points at docs/ARCHITECTURE.md.
- Stack snapshot (React 19, TS 7 native, Vite 8 + Compiler, Zustand 5,
TanStack Query 5, Vitest 3).
- Layout convention spelled out — `src/components/<area>/<feature>/`
for views, `src/hooks/<area>/<feature?>/` flat for hooks. The
rejected-feature-folders decision is the most stepped-on rake, so
it lives here at the top.
- The canonical 3-file god-hook split shape with doorbell as the
reference.
- Patterns to use with copy-pasteable signatures: useNitroEventState,
useMessageEventState, useNitroQuery (with the accept() filter),
Zustand stores via createNitroStore, WidgetErrorBoundary.
- "Wired up vs not yet" matrix: what each pattern is adopted on and
what the next reasonable target is.
- Pointer to the two still-open logic bugs (MainView CREATED/ENDED
race; LayoutFurniImageView async fetch race) with fix shapes.
- House rules: commit author override, no claude/... branch names,
never merge a layout-violating branch, skip-motivated splits are
fine if explained in the commit message.
docs/ARCHITECTURE.md (refresh)
- "What's already in place" rewritten to reflect the full state of
the feat/react19-modernization branch:
* stale references to the old claude/update-react-typescript-He2rs
branch removed
* the three additional god-hook splits done since the last edit
(furni-chooser, user-chooser, friend-request) added
* the 4 useNitroQuery migration sites listed (OfferView,
CatalogLayoutRoomAdsView, ModToolsChatlogView, CfhChatlogView)
* the three additional WiredCreatorToolsView tab extractions
(Monitor, Inspection, Variables) with the 4493 -> 3544 line
counter
* dead-code removal of the legacy login dialogs documented
* the Vitest count updated from 22 to 77 across 6 test files
* usePollSubscriptions hoist to RoomWidgetsView noted
- "How to pick the next refactor PR" rewritten:
* completed items removed (the previous list still had
"hoist usePollSubscriptions" as todo even though it's done,
and "per-tab WiredCreatorTools split" same)
* remaining priorities re-ordered: useCatalog migration (1),
useCatalog split (2), per-widget error boundaries (3),
wired-tools shared-state Zustand slice (4), the two open
logic bugs (5), wider Vitest coverage (6).
* "skipped intentionally" subsection added for the god-hook
splits that need design work first (pet-package, word-quiz,
chat-input, chat-widget, avatar-info).
Verification
- yarn test: 77/77 still passing.
- grep claude/update-react-typescript-He2rs docs/ARCHITECTURE.md: 0
(no stale branch refs).
Now a fresh `claude` session in this repo can read CLAUDE.md, follow
the link to ARCHITECTURE.md, and start contributing without re-asking
the conventions.
|
||
|
|
7218285583 |
Split usePollWidget into subscriptions + actions (proposal #4) + doc update
usePollWidget bundled two unrelated responsibilities:
- three useNitroEvent listeners that bridge RoomSessionPollEvent
(OFFER / ERROR / CONTENT) onto the UI event bus via DispatchUiEvent
— pure side-effects, zero local state, should mount once;
- three imperative actions (startPoll, rejectPoll, answerPoll) that
every consumer wants, but which shouldn't re-register the listeners.
In practice the only consumer of usePollWidget was useWordQuizWidget,
which needed only `answerPoll` — but pulled in the three subscriptions
as a side effect every time the word-quiz widget rendered. That's the
classic god-hook anti-pattern this proposal targets.
Split (mirrors the doorbell pattern already in place):
- src/hooks/rooms/widgets/usePollSubscriptions.ts (new): the three
bridge listeners, returns void. Should be mounted ONCE at the
highest stable level above poll-aware UI (room widgets root). For
now still mounted by the shim — follow-up PR can move it.
- src/hooks/rooms/widgets/usePollActions.ts (new): the three
imperative actions. Defensive `?.` on roomSession so a poll action
during a room transition no longer crashes.
- src/hooks/rooms/widgets/usePollWidget.ts: kept as a deprecated shim
that composes both — preserves the old `{ startPoll, rejectPoll,
answerPoll }` shape so existing consumers don't break.
- src/hooks/rooms/widgets/useWordQuizWidget.ts: migrated to import
usePollActions directly. The word-quiz widget no longer registers
poll subscriptions transitively — its render no longer has the side
effect of subscribing to three renderer events.
Doc
- docs/ARCHITECTURE.md "What's already in place": records both god-hook
splits (doorbell + poll), the now-enabled React Query and Zustand,
and the test infrastructure. Removes the "not yet enabled" markers
for #2 and #5.
- "How to pick the next refactor PR": rewritten to reflect that the
foundations are done. New priority order:
1. migrate useCatalog's read-only fetches to useNitroQuery,
2. hoist usePollSubscriptions to room-session level,
3. split useCatalog along the doorbell/poll lines,
4. broaden Vitest coverage,
5. per-tab WiredCreatorToolsView split.
Verification
- yarn eslint on the touched files: 0 errors / 0 warnings.
- yarn test: 22/22 passing, 2 files, ~1.0s.
- Existing useWordQuizWidget consumers (RoomWidgetsView ->
WordQuizWidgetView) unaffected — they import from the barrel which
still re-exports the same shape.
https://claude.ai/code/session_01GrR87LAqnAEyKG2ZbmQt5Q
|
||
|
|
0755285708 |
Revert feature-folder migration; keep classic src/components + src/hooks layout
Decision: the src/features/<feature>/ layout introduced as proposal #3 (pilot on doorbell in 8ec9d27) is not the convention the team wants. The existing src/components/<area>/ + src/hooks/<area>/ split is the one that stays. What's reverted - src/features/doorbell/ is removed entirely. The doorbell view and the two hooks move back under the classic paths: src/features/doorbell/views/DoorbellWidgetView.tsx -> src/components/room/widgets/doorbell/DoorbellWidgetView.tsx src/features/doorbell/hooks/useDoorbellState.ts -> src/hooks/rooms/widgets/useDoorbellState.ts src/features/doorbell/hooks/useDoorbellActions.ts -> src/hooks/rooms/widgets/useDoorbellActions.ts - The compat shims that lived in those classic paths are dropped now that the real files are back. - src/hooks/rooms/widgets/index.ts adds the two new hooks alongside the existing useDoorbellWidget shim (kept as a deprecated wrapper so any external consumer importing the old shape via the barrel keeps working). What's preserved - The split between data and actions (proposal #4) — useDoorbellState and useDoorbellActions remain two separate hooks. This was the actual improvement, and it's independent of where the files sit. - The bug fixes from 8ec9d27 (close button race, optimistic-remove rollback) — both still present, just in the new path. - src/state/createNitroStore.ts and src/api/nitro-query/createNitroQuery.ts are left where they are. They aren't feature folders; they're cross-cutting framework code (Zustand skeleton, React Query adapter prototype) that any feature can consume. Doc - docs/ARCHITECTURE.md section #3 is rewritten to record the decision rather than recommend the layout. It now describes the convention to follow: * views under src/components/<area>/<feature>/ * hooks under src/hooks/<area>/<feature?>/ (siblings, not subfolders per widget) * sibling .types/.constants/.helpers files for view-specific code (e.g. WiredCreatorTools.*.ts) - "What's already in place" and "Recently fixed" sections updated to point at the new paths. - "How to pick the next refactor PR" no longer mentions feature-folder migration as an option. Note: the five extra feature folders started this session (reconnect, nitropedia, ads, hc-center, campaign) were never committed; they only existed in the working tree and have been restored from HEAD. Verification - find src/features -type f -> 0 (directory removed). - npx tsc --noEmit on all touched files: clean (only the project-wide pre-existing TS2307 about @nitrots/nitro-renderer not installed locally remains, same as before). - npx eslint on all touched files: 0 errors, 0 warnings. https://claude.ai/code/session_01GrR87LAqnAEyKG2ZbmQt5Q |
||
|
|
81656e7b19 |
Fix two logic bugs found while refactoring + document the open ones
These are the bugs surfaced during the structural work that are simple
enough to fix in isolation. Larger ones (race conditions that need
session-token tracking, async-fetch ordering) are deferred and documented
in docs/ARCHITECTURE.md "Known logic bugs" — the repo has Issues
disabled, so the doc is the issue board.
== Fix: room history wiped on every tab close
src/components/room/widgets/room-tools/RoomToolsWidgetView.tsx had a
useEffect that registered a `beforeunload` handler calling
`window.localStorage.removeItem('nitro.room.history')`. The whole point
of localStorage is to persist across sessions; wiping it on tab close is
either a leftover debug call or a misunderstanding of the API.
Removed the handler. History now persists across browser sessions, which
matches user expectations. If "session-only" was the intent, the right
primitive is `sessionStorage` (not localStorage + cleanup) — left as a
note in the doc.
== Fix: AvatarInfoPetTrainingPanelView null-pointer on session change
src/components/room/widgets/avatar-info/AvatarInfoPetTrainingPanelView.tsx
read `roomSession.userDataManager.getPetData(parser.petId)` without
guarding for `roomSession` being null. The PetTrainingPanelMessageEvent
can arrive during a room transition when `roomSession` is briefly null,
crashing the widget. Added `?.` chain on both `roomSession` and
`userDataManager`.
== Doc: known logic bugs section
Two open issues documented for follow-up:
- MainView.tsx CREATED/ENDED race — needs session-token tracking, fits
cleanly into the future useNitroEventReducer companion to proposal #1.
- LayoutFurniImageView / LayoutAvatarImageView async fetch ordering —
needs request-id refs, or solves itself once React Query (proposal #2)
is enabled and the image fetch becomes a query keyed on props.
Plus a "recently fixed" subsection that records the four bugs already
addressed in this branch (doorbell close button, doorbell optimistic
remove, room history wipe, pet panel null-pointer) so the next reader
knows what changed and why.
== Verification
- yarn eslint on the two modified files: same error count before and
after (5 pre-existing set-state-in-effect on RoomToolsWidgetView,
none introduced).
- yarn tsc on the two modified files: clean.
https://claude.ai/code/session_01GrR87LAqnAEyKG2ZbmQt5Q
|
||
|
|
48d62c5c6b |
Architecture refactor: docs + 5 pilot implementations + error boundary
This is the structural plan promised in the previous session, with concrete pilots for all five proposals + the bonus error-boundary work. == docs/ARCHITECTURE.md (new, ~370 lines) Living document describing: - where the project stands today (event-bus pattern friction with React 19, god-hooks, oversized files); - the five proposed structural improvements with the why/how/status of each; - what's already in place across this branch; - recommended order for the next refactor PRs. This is the deliverable the rest of this commit references. == Proposal #3 + #4 pilots: src/features/doorbell/ (new) Concrete feature-folder migration on the doorbell widget (chosen because it's small enough to migrate end-to-end in one commit). src/features/doorbell/ index.ts public API views/DoorbellWidgetView.tsx hooks/useDoorbellState.ts reduces 3 events into a users array (data only) hooks/useDoorbellActions.ts answer(name, flag) (imperative actions only) The split (data vs actions) is the pattern proposal #4 wants applied to useCatalog/useChat/useWiredTools later. The original useDoorbellWidget had both concerns + a buggy `useEffect(() => setIsVisible(!!users.length), [users])` derive-state-in-effect. The new view computes visibility in render. Compat shims kept so existing imports keep working: - src/components/room/widgets/doorbell/DoorbellWidgetView.tsx -> 1-line re-export - src/hooks/rooms/widgets/useDoorbellWidget.ts -> deprecated wrapper around the two new hooks, returning the same { users, answer } shape. == Proposal #2 prototype: src/api/nitro-query/ (new) Adapter outline for wrapping composer/parser request-response pairs in TanStack Query. Not yet enabled because @tanstack/react-query is not in package.json. The file documents the activation steps: yarn add @tanstack/react-query @tanstack/react-query-devtools + mount QueryClientProvider in src/index.tsx awaitNitroResponse() throws with a helpful pointer to the doc section if called before activation, so accidental adoption fails loudly. == Proposal #5 skeleton: src/state/createNitroStore.ts (new) Same pattern: skeleton + activation instructions. Not yet enabled because zustand is not in package.json. yarn add zustand + replace the throw with `import { create } from 'zustand'; export const createNitroStore = create;` The doc inside the file shows the recommended slice shape and points to the suggested first migration target (the let isCreatingRoom singleton in NavigatorRoomCreatorView). == Bonus: WidgetErrorBoundary src/common/error-boundary/WidgetErrorBoundary.tsx wraps react-error-boundary with a sensible default (silent fallback, NitroLogger.error). Re-exported from src/common/index.ts. Applied as the umbrella around RoomWidgetsView's children — a widget crash in a room (e.g. malformed pet data) now degrades gracefully instead of unmounting the whole UI. == Verification - yarn eslint on all new + modified files: 0 errors / 0 warnings introduced. RoomWidgetsView still has its 1 pre-existing FC<{}> error (1 before, 1 after). - yarn tsc on all new files: clean (only project-wide pre-existing TS2307 about @nitrots/nitro-renderer not installed locally remains). - No regressions: existing imports of DoorbellWidgetView and useDoorbellWidget keep resolving via the compat shims. == What's NOT in this commit (intentionally) - Mass adoption of the new patterns elsewhere — left as follow-up PRs in the order documented in ARCHITECTURE.md "How to pick the next refactor PR". - Installation of @tanstack/react-query / zustand — explicit team decision, not the LLM's to make. - Test infrastructure (Vitest setup) — listed as the #1 missing piece in the doc, but a separate PR. https://claude.ai/code/session_01GrR87LAqnAEyKG2ZbmQt5Q |