| name | app-renderer-systems |
| description | Guides creation and modification of domain feature systems organized under a systems/ directory. Covers directory layout, API service layer patterns, TanStack Query and DB collection setup, React context and XState store conventions, hook organization, and public API barrel exports. Use when adding a new domain system, extending an existing one, or fixing bugs in a system-layer codebase. Don't use for generic React component work, backend API implementation, or codebases not organized around a systems/ domain pattern. |
| allowed-tools | Read, Grep, Glob |
Feature Systems Guide
A "system" is a self-contained, domain-driven module that owns everything related to one domain: its API calls, data layer, hooks, components, and public API. Systems live under a systems/<domain>/ directory.
Read references/directory-layout.md for the full directory structure and naming conventions.
Read references/patterns.md for annotated implementation patterns per layer.
Quick Reference
Mandatory Companion Skills
Activate alongside this skill — systems span multiple technical domains:
| Situation | Activate |
|---|
| Any hook or component | react + tanstack |
| TanStack DB collection | tanstack |
| XState store | xstate |
| Utility functions | es-toolkit |
| Writing/fixing tests | test-antipatterns + vitest |
| Bug fix | systematic-debugging + no-workarounds |
System Directory at a Glance
systems/<domain>/
├── index.ts # Public API barrel — required for every system
├── types.ts # TypeScript types for this domain
├── adapters/ # API service layer (HTTP calls, error types)
│ └── <domain>-api.ts
├── lib/ # Pure utilities, schemas, constants, query keys
│ ├── query-keys.ts # TanStack Query key factory
│ ├── <domain>-schemas.ts
│ └── constants.ts
├── db/ # TanStack DB collections (reactive/optimistic data)
│ └── <domain>-collection.ts
├── hooks/ # React hooks
│ ├── __tests__/
│ └── use-<action>.ts
├── contexts/ # React contexts + providers
│ └── <domain>-context.tsx
├── stores/ # XState stores (complex async state machines)
│ └── <domain>-store.ts
├── components/ # React UI components
│ ├── stories/
│ └── index.ts
└── guards/ # Route guards / access checks
Step-by-Step: Creating a New System
Step 1 — Define types.ts
- Export clean domain types; never expose raw API response shapes.
- Derive from the project's API contract types when available.
- Document complex aggregated types with JSDoc explaining derivation rules and invariants.
Step 2 — Build the API service layer
- Create
adapters/<domain>-api.ts.
- Use the project's HTTP client for API calls.
- Export a single namespace object:
export const <domain>Api = { list, create, update, delete }.
- Export a typed error class:
export class <Domain>ApiError extends Error { ... }.
- Accept
signal?: AbortSignal on every function to support query cancellation.
- Keep all internal helpers (error extraction, response normalization) private to the module.
Step 3 — Add lib/query-keys.ts
export const <domain>Keys = {
all: ["<domain>"] as const,
list: (scopeId: string | null) => [...<domain>Keys.all, "list", scopeId] as const,
detail: (id: string) => [...<domain>Keys.all, "detail", id] as const,
};
- Scope keys with any identifier (userId, orgId, etc.) that isolates the cache correctly.
- Use
as const on every key tuple.
Step 4 — (Optional) Create a DB collection
Create db/<domain>-collection.ts only when data needs optimistic updates or fine-grained reactivity. Read references/patterns.md for the full pattern.
Key rules:
- Export
create<Domain>Collection(scopeId) factory function.
- Export
<domain>CollectionKey(scopeId) for external registry/cache keying.
- Export
type <Domain>CollectionInstance = ReturnType<typeof create<Domain>Collection>.
- Handlers (
onInsert, onUpdate, onDelete) call the API service and sync the query cache.
- Always return
{ refetch: false } from handlers to avoid double-fetches.
Step 5 — Write hooks
- Query hooks: Wrap
useQuery or queryOptions; accept a scope ID + optional { enabled? }.
- Mutation hooks: Accept
collection: <Domain>CollectionInstance as a parameter — never create a new collection internally.
- View-model hooks: Compose multiple hooks for a page/shell component; return a flat object.
- Place tests in
hooks/__tests__/ or co-locate as use-xxx.test.tsx.
Step 6 — (Optional) Add context
Create contexts/<domain>-context.tsx when a collection instance or combined state must be shared across a component subtree.
export const <Domain>Context = createContext<<Domain>ContextValue | null>(null);
- Export the context, provider component, and re-export consumer hooks from the same file.
- For performance-sensitive trees, split into Core / UI / Operations sub-contexts.
Step 7 — (Optional) Add an XState store
Create stores/<domain>-store.ts for complex async state machines (multi-step flows, polling, event emission).
export const <domain>Store = createStore({
context: { ... } as <Domain>Context,
emits: { ... },
on: {
someEvent: (context, event, enqueue) => {
enqueue.effect(async () => { ... });
return { ...context, isLoading: true };
},
},
});
Step 8 — Wire up index.ts
Organize the barrel with labeled sections and explicit named exports:
export type { <Domain>Type } from "./types";
export { use<Domain>, use<Domain>Mutation } from "./hooks";
export { <Domain>Component } from "./components";
export { <domain>HelperFn } from "./lib/<domain>-utils";
export { <domain>Keys } from "./lib/query-keys";
export { <domain>Api, <Domain>ApiError } from "./adapters/<domain>-api";
Critical Rules
- Never create collection instances in mutation hooks. Mutation hooks receive
collection as a parameter. Creating a new collection internally causes "key not found" errors at runtime.
- Unidirectional dependency flow.
adapters → lib → db → hooks → components. Adapters never import from hooks or components.
- Scope query keys. Any query depending on an authenticated scope (user, org, tenant) must include that scope ID in its key to prevent stale cross-scope data.
- Typed errors in the API layer. Never throw raw errors from adapters. Use a typed error class so consumers can distinguish error types without inspecting message strings.
- AbortSignal propagation. Pass
signal from the queryFn context through to every API call for proper query cancellation.
- Zod schemas in lib/. Place all Zod schemas in
lib/<domain>-schemas.ts. Collections reference them via the schema option for runtime validation.
Error Handling
- API layer throws typed error: TanStack Query catches and exposes it via
query.error.
- Collection handler throws: TanStack DB automatically rolls back the optimistic update.
- "key not found" in collection: A new collection instance was created inside a mutation hook instead of receiving the shared one — fix the hook signature.
- Stale cross-scope data: Add the scope ID to the query key and verify that
enabled guards check Boolean(scopeId).
Detailed References
references/directory-layout.md — Full directory structure, file naming, and barrel conventions
references/patterns.md — Annotated code patterns for the API layer, collections, hooks, contexts, and stores