From f75762a2db5accdb6a8e822cd447cafc318a2b85 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 11 May 2026 18:05:12 +0000 Subject: [PATCH] Add CLAUDE.md + refresh docs/ARCHITECTURE.md to current state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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///` for views, `src/hooks///` 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. --- CLAUDE.md | 215 +++++++++++++++++++++++++++++++++++++++++++ docs/ARCHITECTURE.md | 209 ++++++++++++++++++++++++++++++----------- 2 files changed, 372 insertions(+), 52 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..638df31 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,215 @@ +# Claude Code — project memory for Nitro-V3 + +This file is read automatically by Claude Code at session start. It captures +the conventions and current state of this branch so a new session can hit +the ground running. + +## TL;DR + +This branch — **`feat/react19-modernization`** — is a long-running modernization +of the Nitro V3 client: bump to React 19.2 idioms, add the supporting +infrastructure (TanStack Query, Zustand, Vitest, React Compiler, error +boundaries), split a few god-hooks, and audit logic bugs along the way. +PR is **#2** on `simoleo89/Nitro-V3`. + +Detailed status, decisions, and next steps live in **`docs/ARCHITECTURE.md`** — +read that before starting anything non-trivial. + +## Commands + +| Goal | Command | +|---|---| +| Dev server | `yarn start` | +| Production build | `yarn build` | +| Lint | `yarn eslint` | +| Type-check (TS 7 native, fast) | `yarn typecheck` | +| Test (Vitest, once) | `yarn test` | +| Test (watch) | `yarn test:watch` | + +The renderer SDK (`@nitrots/nitro-renderer`) is consumed via a filesystem +link to a sibling working tree — `../Nitro_Render_V3` (preferred) or +`../renderer` (legacy). Without it, `yarn typecheck` reports TS2307 across +the codebase — that's expected on a sandbox without the renderer, not a +regression. + +## Stack snapshot + +- React 19.2.5, `react-dom` 19.2.5, `@types/react` 19.2.x. +- TypeScript: TS 6 for build, **TS 7 native preview** (`@typescript/native-preview`, + invoked via `tsgo`) for the `typecheck` script. +- Vite 8 + `@vitejs/plugin-react` 6 + `babel-plugin-react-compiler` 1.0. +- ESLint 10 + `typescript-eslint` 8 + `eslint-plugin-react-hooks@7` + + `eslint-plugin-react-compiler`. +- TanStack Query 5 (`@tanstack/react-query` + devtools). +- Zustand 5. +- Vitest 3 + jsdom + `@testing-library/react` + `@testing-library/jest-dom`. +- `react-error-boundary` 6. + +## Layout convention (DO NOT CHANGE) + +Established by the team and recorded in `docs/ARCHITECTURE.md` proposal #3 +(rejected the `src/features/` alternative). Stay on this layout — every PR +that violates it will need to be reworked. + +``` +src/components/// → views (.tsx only) + e.g. src/components/room/widgets/doorbell/DoorbellWidgetView.tsx + +src/hooks/// → hooks, FLAT files, no per-feature subfolder + e.g. src/hooks/rooms/widgets/useDoorbellState.ts + src/hooks/rooms/widgets/useDoorbellActions.ts + src/hooks/rooms/widgets/useDoorbellWidget.ts (deprecated shim) + +src/api/ → cross-cutting helpers (LocalizeText, composers, formatters) +src/common/ → reusable UI primitives + error boundary +src/state/ → Zustand stores (cross-feature only) +tests/ → Vitest suites (mirror filename of subject) +``` + +When splitting a god-hook the convention is **3 files, all flat in the +hooks barrel directory**: + +- `useState.ts` — state + event subscriptions + derived values +- `useActions.ts` — pure imperative actions (no state writes) +- `useWidget.ts` — deprecated wrapper that composes the two and + preserves the old return shape so existing consumers don't break + +See `useDoorbellState`/`useDoorbellActions`/`useDoorbellWidget` as the +canonical pattern. + +## Patterns to use + +### `useNitroEventState` / `useMessageEventState` + +For "derived state from a single event" replace the two-step +`useState + useNitroEvent(e => setState(...))` with a single call: + +```ts +const foo = useNitroEventState(SomeEvent, e => e.payload, initial); +const data = useMessageEventState(SomeParser, e => e.getParser()?.field ?? null, null); +``` + +The selector is held in a `useLayoutEffect`-refreshed ref so the listener +stays registered across renders. Both hooks are exported from +`src/hooks/events`. + +### `useNitroQuery` + +For composer/parser request-response pairs: + +```ts +const { data } = useNitroQuery({ + key: ['nitro', 'domain', 'request', ...args], + request: () => new SomeComposer(args), + parser: SomeParser, + select: e => e.getParser()?.data, + accept: e => e.getParser()?.correlationKey === args, // optional, for shared event bus + staleTime: 60_000, +}); +``` + +Already wired up; `QueryClientProvider` is mounted in `src/index.tsx`. +Adopted on `OfferView`, `CatalogLayoutRoomAdsView`, `ModToolsChatlogView`, +`CfhChatlogView`. + +### Zustand stores + +For cross-feature UI state (avoid module-level `let`): + +```ts +import { createNitroStore } from '@/state/createNitroStore'; + +export const useFooStore = createNitroStore()((set) => ({ + ... +})); +``` + +Components subscribe to slices, not the whole store: + +```ts +const value = useFooStore(s => s.value); +``` + +First adoption: `src/components/navigator/views/navigatorRoomCreatorStore.ts`. + +### `WidgetErrorBoundary` + +Wrap any in-room widget tree so a crash degrades gracefully (logs to +NitroLogger, falls back to `null`). Already applied at `RoomWidgetsView` +as an umbrella; per-widget wrapping is a follow-up. + +```tsx + + + +``` + +### Form Actions + +Login / Register / Forgot in `src/components/login/LoginView.tsx` use +`useActionState` + `useFormStatus`. The legacy non-Action versions in +`src/components/login/components/{Register,Forgot}Dialog.tsx` and +`shared.ts` have been **removed** (dead code). + +## What's wired up and what isn't + +| Adopted | Pilot sites | +|---|---| +| `useNitroEventState` | `OfferView` | +| `useNitroQuery` | `OfferView`, `CatalogLayoutRoomAdsView`, `ModToolsChatlogView`, `CfhChatlogView` | +| Zustand | `NavigatorRoomCreatorView` (`useRoomCreatorStore`) | +| God-hook split | `doorbell`, `poll`, `furni-chooser`, `user-chooser`, `friend-request` | +| `WidgetErrorBoundary` | `RoomWidgetsView` umbrella | +| Vitest | 77/77 cases on pure helpers + the Zustand store | + +| Not yet | Notes | +|---|---| +| Split `useCatalog` (~1100 LOC) | Migrate read-only fetches to `useNitroQuery` first, then split into `useCatalogData` / `useCatalogUiState` / `useCatalogActions`. | +| Split `useChatInputWidget` / `useChatWidget` / `useAvatarInfoWidget` | Large state machines; needs careful per-file design before mechanical split. | +| Split `usePetPackageWidget` / `useWordQuizWidget` | Their "actions" mutate internal state; need to either pass args or hoist state to a store first. Documented in commit messages, skipped intentionally. | +| Hoist Wired Creator Tools shared state to a Zustand slice | Would remove ~25 props passed to the 3 tab sub-components. | +| Wider Vitest coverage (React components) | `@testing-library/*` is installed; needs a small renderer-SDK mock layer first. | + +## Known open logic bugs + +Read `docs/ARCHITECTURE.md` "Known logic bugs" section. The two still-open +ones: + +- `MainView.tsx:47-48` — race between `RoomSessionEvent.CREATED` and `ENDED` + (no session token guard). +- `LayoutFurniImageView` / `LayoutAvatarImageView` — async fetch race when + props change twice in quick succession. + +Fix shapes documented; both are reasonable PRs on their own. + +## House rules + +- **Commit author**: `simoleo89 `. + When committing, pass these via per-command overrides + (`git -c user.name=simoleo89 -c user.email=...`) — do NOT modify the + global git config. +- **No `claude/...` branch names** — auto-generated names should be + renamed before pushing. Prefer `feat/`. +- **Never merge a branch that violates the layout convention** above. + The `feat/react19-hooks-adapter` branch (deleted) put hooks under + `src/components/...`; that's wrong and a recurring temptation. +- **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 77/77. +- **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. + +## Where everything lives + +- Architecture doc: `docs/ARCHITECTURE.md` +- Test runner config: `vitest.config.mts` (separate from `vite.config.mjs`) +- Test setup: `tests/setup.ts` +- React Query adapter: `src/api/nitro-query/createNitroQuery.ts` +- Zustand factory: `src/state/createNitroStore.ts` +- Error boundary: `src/common/error-boundary/WidgetErrorBoundary.tsx` +- Event hooks (`useNitroEvent`, `useMessageEvent`, `useNitroEventState`, + `useMessageEventState`): `src/hooks/events/` +- Wired-tools split (types/constants/helpers + 3 tab views): + `src/components/wired-tools/` diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index bde6d49..950901b 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -296,70 +296,175 @@ failures. ## What's already in place -The current branch (`claude/update-react-typescript-He2rs`) has applied: +The current branch (**`feat/react19-modernization`**, PR #2) has applied: -- **React 19.2 / TypeScript 7 (Native preview) / ESLint 10 / React Hooks v7 / React Compiler 1.0** — toolchain bump, all warnings audited. -- **Form Actions** — `
` + `useActionState` adopted in - `LoginView.tsx` (login, register, forgot dialogs). -- **`useEffectEvent`** — adopted in `App.tsx`, `FurniEditorSearchView`, - `NotificationBadgeReceivedBubbleView`, `NavigatorRoomSettingsRightsTabView`, - `UiSettingsContext` to clear all `react-hooks/exhaustive-deps` warnings. -- **Targeted `set-state-in-effect` cleanup** — `CatalogHeaderView` (pure - derive), `NavigatorRoomCreatorView` (lazy state init), `LoginView` +### Toolchain +- React 19.2 / `react-dom` 19.2 / `@types/react` 19.2. +- TS 6 for build + **TS 7 native preview** (`tsgo`) for `yarn typecheck`. +- ESLint 10 + `typescript-eslint` 8 + `eslint-plugin-react-hooks@7` + + `eslint-plugin-react-compiler`. +- Vite 8 + React Compiler 1.0 (`babel-plugin-react-compiler`). +- `` mounted; `App.tsx` made idempotent for the double-mount. + +### React 19 idioms +- **`forwardRef` → `ref` prop** on 7 layout/component files (11 call sites). +- **`` → ``** on 6 contexts. +- **Native `