mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
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.
This commit is contained in:
@@ -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/<area>/<feature>/ → views (.tsx only)
|
||||
e.g. src/components/room/widgets/doorbell/DoorbellWidgetView.tsx
|
||||
|
||||
src/hooks/<area>/<feature?>/ → 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**:
|
||||
|
||||
- `use<Feature>State.ts` — state + event subscriptions + derived values
|
||||
- `use<Feature>Actions.ts` — pure imperative actions (no state writes)
|
||||
- `use<Feature>Widget.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<SomeParser, SomeData>({
|
||||
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<FooState>()((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
|
||||
<WidgetErrorBoundary name="ChatWidget">
|
||||
<ChatWidgetView />
|
||||
</WidgetErrorBoundary>
|
||||
```
|
||||
|
||||
### 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 <simoleo89@users.noreply.github.com>`.
|
||||
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/<description>`.
|
||||
- **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/`
|
||||
Reference in New Issue
Block a user