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>
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>
Today if you clone Nitro-V3 without cloning Nitro_Render_V3 next to it,
yarn start / yarn build fail deep inside Rolldown with:
Failed to resolve import "@nitrots/nitro-renderer"
from "/path/to/Nitro-V3/src/App.tsx"
which doesn't tell you what to do. Move the check up to
vite.config.mjs: when neither ../Nitro_Render_V3 nor ../renderer
exists, throw with the explicit clone-and-install steps and a pointer
to CLAUDE.md.
Also update CLAUDE.md "Commands" section:
- Add `yarn preview` (production build server, http://localhost:4173).
- Add a 4-step "Setup walkthrough" covering: clone the renderer
sibling, yarn install on both, copy public/configuration/*.example
to *.json, then run.
Net effect: a fresh checkout of this branch shows you exactly which
prerequisite is missing instead of a Rolldown stack trace.
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.
useNotification is consumed by ~44 sites in the codebase but most of
them only need a single imperative entry point (typically simpleAlert
or showConfirm). The hook also runs ~24 useMessageEvent listeners
internally to translate server events into queued notifications.
Same singleton-filter pattern as useWiredTools / useTranslation:
- useNotificationStore (internal, was useNotificationState) — the
previous body unchanged. ~30 listeners + 5 state slices + 8 actions
in one closure.
- useNotificationState (public, read-only) — useBetween filter
exposing only the three queue arrays (alerts, bubbleAlerts,
confirms). Used by the global NotificationView renderer.
- useNotificationActions (public, imperative) — useBetween filter
exposing the 8 entry points: simpleAlert / showNitroAlert /
showTradeAlert / showConfirm / showSingleBubble +
closeAlert / closeBubbleAlert / closeConfirm.
- useNotification (deprecated shim) — composes the singleton via
useBetween, preserving the historical return shape so the 44
existing call sites keep working.
Also brings CLAUDE.md's 'What's wired up' table up to date with the
splits done this session (chat-input doorbell-style, wired-tools +
translation singleton-filter, plus this notification one) and the
8 useCatalog fetch migrations to TanStack queries.
- 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).
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.