Skip to content

Commit 4b15710

Browse files
committed
Refine llms.txt with detailed guidelines for UI state management, event handling, and devtools setup. Introduce a new section on testing events and subscriptions, emphasizing best practices for handler extraction and mock inputs.
1 parent 5507a3b commit 4b15710

1 file changed

Lines changed: 124 additions & 7 deletions

File tree

llms.txt

Lines changed: 124 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ Rules:
3333
## 2) State Shape
3434

3535
- Grow horizontally (new top-level feature keys), avoid deep nesting.
36-
- Normalize entity-like data (`byId` maps + id arrays) for fast lookup and simpler updates.
37-
- Keep UI state explicit and separate from domain entities.
36+
- Keep UI state explicit and separate from domain entities. Use a plain prefix on field names to group by domain (e.g. `uiSidebarOpen`, `uiSelectedTab`, `uiModalVisible` for UI state; `todosMap`, `todosFilter` for domain state). Avoid separators like `/` or `.` in field names — flat `camelCase` with a domain prefix keeps keys simple, grep-friendly, and avoids issues with object path accessors.
3837
- If using `Map`/`Set` in DB, call `enableMapSet()` before `initAppDb`.
3938

4039
## 3) Events `regEvent`
@@ -76,6 +75,8 @@ Event naming:
7675
- subscribe to minimal required data
7776
- dispatch events on user intent (pass only user-provided values — e.g. input text, selected id — never forward subscription data back through dispatch; the event handler should read everything it needs from the DB itself)
7877
- render UI
78+
- Always pass the component name as the second argument to `useSubscription` for devtools tracing:
79+
`const todos = useSubscription<Todo[]>([SUB_IDS.TODOS], 'TodoList');`
7980
- Never transform, filter, sort, or reshape subscription data inside a component — if the view needs a different shape, create a dedicated subscription that returns it ready to render.
8081
- Use direct React hooks only for local/ephemeral component concerns:
8182
- temporary form/input draft state before dispatch
@@ -87,13 +88,129 @@ Event naming:
8788
- Do not place business rules/validation pipelines in component handlers.
8889
- Avoid over-subscription (row/item components should not subscribe to full collections).
8990

90-
## 7) Test Minimum
91+
## 7) Devtools Setup
92+
93+
Install devtools as a dev dependency:
94+
95+
```bash
96+
npm i -D @flexsurfer/reflex-devtools
97+
```
98+
99+
Enable in your app entry point (`main.tsx`) before dispatching any events:
100+
101+
```ts
102+
import { dispatch, enableTracing } from '@flexsurfer/reflex';
103+
import { enableDevtools } from '@flexsurfer/reflex-devtools';
104+
105+
enableTracing(); // required — turns on the trace pipeline that devtools reads
106+
enableDevtools(); // opens the devtools connection
107+
108+
dispatch([EVENT_IDS.APP_INIT]);
109+
```
110+
111+
`enableTracing()` activates internal trace collection for events, subscriptions, and renders. `enableDevtools()` hooks into those traces and exposes them to the devtools UI / MCP server. Both calls are cheap no-ops in production when tree-shaken behind a `process.env.NODE_ENV` guard.
112+
113+
If your app uses Vite with local source aliases (monorepo / examples), make sure reflex-devtools resolves the same Reflex instance:
114+
115+
```ts
116+
// vite.config.ts
117+
resolve: {
118+
alias: {
119+
'@flexsurfer/reflex': path.resolve(__dirname, '../../src')
120+
}
121+
},
122+
optimizeDeps: {
123+
exclude: ['@flexsurfer/reflex-devtools']
124+
}
125+
```
126+
127+
## 8) Test Minimum
91128

92129
For every new feature:
93130
- Event tests: mutation correctness + emitted effect tuples.
94131
- Subscription tests: derived outputs from fixed state fixtures.
95132

96-
## 8) AI Generation Checklist
133+
Events and subscriptions are pure functions — test them by extracting the handler with `getHandler` and calling it directly with mock inputs.
134+
135+
### Testing Events
136+
137+
Use `getHandler('event', EVENT_ID)` to get the raw handler. Build a mock `coeffects` object with `draftDb` (and any injected coeffects), call the handler, then assert mutations on `draftDb` and the returned effect tuples.
138+
139+
```ts
140+
import { getHandler } from '@flexsurfer/reflex';
141+
import type { EventHandler, CoEffects } from '@flexsurfer/reflex';
142+
import './events'; // side-effect import registers handlers
143+
144+
it('ADD_TODO should add a todo and persist', () => {
145+
const handler = getHandler('event', EVENT_IDS.ADD_TODO) as EventHandler;
146+
147+
const mockDB = { todos: [], showing: 'all' };
148+
const coeffects = {
149+
event: [EVENT_IDS.ADD_TODO, 'Buy milk'],
150+
draftDb: mockDB,
151+
now: 12345,
152+
} as CoEffects;
153+
154+
const effects = handler(coeffects, 'Buy milk');
155+
156+
// Assert state mutation
157+
expect(mockDB.todos).toHaveLength(1);
158+
expect(mockDB.todos[0]).toEqual({ id: 12345, title: 'Buy milk', done: false });
159+
160+
// Assert emitted effects
161+
expect(effects).toEqual([[EFFECT_IDS.SET_TODOS, mockDB.todos]]);
162+
});
163+
164+
it('ADD_TODO should ignore empty input', () => {
165+
const handler = getHandler('event', EVENT_IDS.ADD_TODO) as EventHandler;
166+
167+
const mockDB = { todos: [], showing: 'all' };
168+
const coeffects = { event: [EVENT_IDS.ADD_TODO, ' '], draftDb: mockDB, now: 1 } as CoEffects;
169+
170+
const effects = handler(coeffects, ' ');
171+
172+
expect(mockDB.todos).toHaveLength(0);
173+
expect(effects).toBeUndefined();
174+
});
175+
```
176+
177+
### Testing Subscriptions
178+
179+
Root subscriptions: call `initAppDb(mockDB)` then call `handler()` with no arguments.
180+
Computed subscriptions: call `handler(inputFromParentSub1, inputFromParentSub2, ...)` directly — the handler receives its parent subscription values as positional arguments.
181+
Dependency checks: use `getHandler('subDeps', SUB_ID)` to verify the subscription's input signals.
182+
183+
```ts
184+
import { getHandler, initAppDb } from '@flexsurfer/reflex';
185+
import type { SubHandler, SubDepsHandler } from '@flexsurfer/reflex';
186+
import './subs';
187+
188+
it('TODOS root sub returns todos from db', () => {
189+
const handler = getHandler('sub', SUB_IDS.TODOS) as SubHandler;
190+
const mockDB = { todos: [{ id: 1, title: 'Test', done: false }] };
191+
initAppDb(mockDB);
192+
193+
expect(handler()).toBe(mockDB.todos);
194+
});
195+
196+
it('OPEN_COUNT computed sub counts active todos', () => {
197+
const handler = getHandler('sub', SUB_IDS.TODOS_OPEN_COUNT) as SubHandler;
198+
const todos = [
199+
{ id: 1, title: 'A', done: false },
200+
{ id: 2, title: 'B', done: true },
201+
{ id: 3, title: 'C', done: false },
202+
];
203+
204+
expect(handler(todos)).toBe(2);
205+
});
206+
207+
it('OPEN_COUNT has correct dependency chain', () => {
208+
const depsHandler = getHandler('subDeps', SUB_IDS.TODOS_OPEN_COUNT) as SubDepsHandler;
209+
expect(depsHandler()).toEqual([[SUB_IDS.TODOS]]);
210+
});
211+
```
212+
213+
## 9) AI Generation Checklist
97214

98215
Before finalizing generated code:
99216
- IDs added/updated in all relevant `*-ids.ts` files.
@@ -106,7 +223,7 @@ Before finalizing generated code:
106223
- Dispatch calls from components pass only user intent, not subscription data; events read from DB.
107224
- Tests added for new event/subscription behavior.
108225

109-
## 9) Starter Skeleton (copy pattern)
226+
## 10) Starter Skeleton (copy pattern)
110227

111228
```ts
112229
import {
@@ -179,8 +296,8 @@ regSub(
179296
);
180297

181298
// Usage example (React):
182-
// const todos = useSubscription([SUB_IDS.TODOS_LIST]);
183-
// const openCount = useSubscription([SUB_IDS.TODOS_OPEN_COUNT]);
299+
// const todos = useSubscription([SUB_IDS.TODOS_LIST], 'TodoList');
300+
// const openCount = useSubscription([SUB_IDS.TODOS_OPEN_COUNT], 'TodoFooter');
184301
// dispatch([EVENT_IDS.APP_INIT]);
185302
// dispatch([EVENT_IDS.TODOS_ADD, 'Buy milk']);
186303
```

0 commit comments

Comments
 (0)