-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathllms.txt
More file actions
272 lines (214 loc) · 10.2 KB
/
llms.txt
File metadata and controls
272 lines (214 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
## 0) Reflex Quick Info
- Reflex is a React state-management library inspired by ClojureScript re-frame (event-driven updates, subscriptions for derived data, effects/coeffects for side effects).
- Install:
- Runtime: `npm i @flexsurfer/reflex`
- Devtools (dev only): `npm i -D @flexsurfer/reflex-devtools`
- Docs:
- Main docs: [reflex.js.org/docs](https://reflex.js.org/docs)
- Best practices: [reflex.js.org/docs/best-practices.html](https://reflex.js.org/docs/best-practices.html)
- Packages: [@flexsurfer/reflex](https://www.npmjs.com/package/@flexsurfer/reflex), [@flexsurfer/reflex-devtools](https://www.npmjs.com/package/@flexsurfer/reflex-devtools)
## 1) State Architecture
Use this baseline structure:
```text
src/state/
db.ts
event-ids.ts
events.ts
effect-ids.ts
effects.ts
sub-ids.ts
subs.ts
```
Rules:
- Keep IDs centralized in `event-ids.ts`, `effect-ids.ts`, `sub-ids.ts`.
- Keep init in `db.ts` (`initAppDb(...)`).
- Register events/effects/subscriptions via side-effect imports in app bootstrap (`main.tsx` style).
- If `events.ts` or `subs.ts` grows too large, split by feature, keep shared/global state separate.
## 2) State Shape
- Grow horizontally (new top-level feature keys), avoid deep nesting.
- 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.
- If using `Map`/`Set` in DB, call `enableMapSet()` before `initAppDb`.
## 3) Events `regEvent`
- Events must be synchronous and focused on state transitions.
- Events should read all required data from `draftDb` (or via coeffects) — never rely on callers passing subscription-derived state; dispatch calls from views should only carry user intent (IDs, input values, flags).
- Validate inputs and guard clauses first; return early on invalid state.
- Mutate only required fields on `draftDb`.
- Avoid unnecessary object/array recreation (`{...obj}`, `[...arr]`) when no actual change is needed.
- Never perform async/API/localStorage work directly in events.
- Return effect tuples for side effects.
- When sending mutated draft data to effects, always use `current(...)`.
- Prefer deterministic coeffects (time/id/random/env) instead of direct globals for testability.
Event naming:
- Keep exported constant keys in `UPPER_SNAKE_CASE`.
- Use namespaced string values: `feature/action` (example: `bases/create`).
- Keep key and value aligned:
- `BASES_CREATE` -> `bases/create`
- `PRODUCTION_PLAN_ADD_BUILDINGS_TO_BASE` -> `production_plan/add_buildings_to_base`
## 4) Effects and Coeffects
- Put all I/O here: localStorage, HTTP, timers, analytics, navigation.
- Effects should be small, defensive, and fail-soft (log, do not crash app state flow).
## 5) Subscriptions `regSub`
- Define root subscriptions first `regSub(id, "pathKey")`.
- Build derived subscriptions from other subscriptions only.
- Use parameterized subscriptions for by-id and section-specific queries.
- Keep subscriptions deterministic and lightweight.
- Subscriptions must return data shaped and ready for direct view consumption — all filtering, sorting, formatting, and joining should happen in the subscription layer, not in React components.
- Move heavy computations to events (precompute once, read many); avoid repeated expensive derivations in hot subscriptions.
## 6) React Component Contract
- Components should only:
- subscribe to minimal required data
- 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)
- render UI
- Always pass the component name as the second argument to `useSubscription` for devtools tracing:
`const todos = useSubscription<Todo[]>([SUB_IDS.TODOS], 'TodoList');`
- 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.
- Use direct React hooks only for local/ephemeral component concerns:
- temporary form/input draft state before dispatch
- UI-only toggles scoped to one component (hover/open/focus)
- refs, DOM measurement, animation lifecycle
- Do not mirror Reflex global state in `useState`/`useReducer`.
- If state is shared, persisted, or business-relevant, keep it in Reflex DB via events/subscriptions.
- Keep `useEffect` thin in components; business side effects belong in Reflex effects/coeffects.
- Do not place business rules/validation pipelines in component handlers.
- Avoid over-subscription (row/item components should not subscribe to full collections).
## 7) Devtools Setup
Install devtools as a dev dependency:
```bash
npm i -D @flexsurfer/reflex-devtools
```
Enable in your app entry point (`main.tsx`) before dispatching any events:
```ts
import { enableTracing } from '@flexsurfer/reflex';
import { enableDevtools } from '@flexsurfer/reflex-devtools';
enableTracing(); // required — turns on the trace pipeline that devtools reads
enableDevtools(); // opens the devtools connection
```
this code should be run only in dev env.
## 8) Test Minimum
For every new feature:
- Event tests: mutation correctness + emitted effect tuples.
- Subscription tests: derived outputs from fixed state fixtures.
Events and subscriptions are pure functions — test them by extracting the handler with `getHandler` and calling it directly with mock inputs.
### Testing Events
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.
```ts
import { getHandler } from '@flexsurfer/reflex';
import type { EventHandler, CoEffects } from '@flexsurfer/reflex';
import './events'; // side-effect import registers handlers
it('ADD_TODO should add a todo and persist', () => {
const handler = getHandler('event', EVENT_IDS.ADD_TODO) as EventHandler;
const mockDB = { todos: [], showing: 'all' };
const coeffects = {
event: [EVENT_IDS.ADD_TODO, 'Buy milk'],
draftDb: mockDB,
now: 12345,
} as CoEffects;
const effects = handler(coeffects, 'Buy milk');
// Assert state mutation
expect(mockDB.todos).toHaveLength(1);
expect(mockDB.todos[0]).toEqual({ id: 12345, title: 'Buy milk', done: false });
// Assert emitted effects
expect(effects).toEqual([[EFFECT_IDS.SET_TODOS, mockDB.todos]]);
});
it('ADD_TODO should ignore empty input', () => {
const handler = getHandler('event', EVENT_IDS.ADD_TODO) as EventHandler;
const mockDB = { todos: [], showing: 'all' };
const coeffects = { event: [EVENT_IDS.ADD_TODO, ' '], draftDb: mockDB, now: 1 } as CoEffects;
const effects = handler(coeffects, ' ');
expect(mockDB.todos).toHaveLength(0);
expect(effects).toBeUndefined();
});
```
### Testing Subscriptions
Computed subscriptions: call `handler(inputFromParentSub1, inputFromParentSub2, ...)` directly — the handler receives its parent subscription values as positional arguments.
```ts
import { getHandler, initAppDb } from '@flexsurfer/reflex';
import type { SubHandler, SubDepsHandler } from '@flexsurfer/reflex';
import './subs';
it('OPEN_COUNT computed sub counts active todos', () => {
const handler = getHandler('sub', SUB_IDS.TODOS_OPEN_COUNT) as SubHandler;
const todos = [
{ id: 1, title: 'A', done: false },
{ id: 2, title: 'B', done: true },
{ id: 3, title: 'C', done: false },
];
expect(handler(todos)).toBe(2);
});
```
## 9) AI Generation Checklist
Before finalizing generated code:
- IDs added/updated in all relevant `*-ids.ts` files.
- Event namespaced and descriptive.
- Side effects isolated to effects/coeffects.
- `current(...)` used when passing draft-derived data to effects.
- No unnecessary object/array recreation in events.
- Expensive work not placed in frequently re-run subscriptions.
- Subscriptions return view-ready data; components do not reshape subscription output.
- Dispatch calls from components pass only user intent, not subscription data; events read from DB.
- Tests added for new event/subscription behavior.
## 10) Starter Skeleton (copy pattern)
```ts
import {
initAppDb,
regSub,
regEvent,
regEffect,
regCoeffect,
current,
dispatch,
useSubscription,
} from '@flexsurfer/reflex';
// event-ids.ts
export const EVENT_IDS = {
APP_INIT: 'app/init',
TODOS_ADD: 'todos/add',
} as const;
// effect-ids.ts
export const EFFECT_IDS = {
GET_TODOS: 'storage/get_todos',
SET_TODOS: 'storage/set_todos',
} as const;
// sub-ids.ts
export const SUB_IDS = {
TODOS_LIST: 'todos/list', // root sub
TODOS_OPEN_COUNT: 'todos/open_count', // computed sub
} as const;
// db.ts
type Todo = { id: string; text: string; done: boolean };
initAppDb({ todos: [] as Todo[] });
// effects.ts
regEffect(EFFECT_IDS.SET_TODOS, (todos: Todo[]) => {
localStorage.setItem('todos', JSON.stringify(todos));
});
regCoeffect(EFFECT_IDS.GET_TODOS, (coeffects) => {
const raw = localStorage.getItem('todos');
coeffects.localStoreTodos = raw ? (JSON.parse(raw) as Todo[]) : [];
return coeffects;
});
// events.ts
regEvent(
EVENT_IDS.APP_INIT,
({ draftDb, localStoreTodos }) => {
draftDb.todos = Array.isArray(localStoreTodos) ? localStoreTodos : [];
},
[[EFFECT_IDS.GET_TODOS]]
);
regEvent(EVENT_IDS.TODOS_ADD, ({ draftDb }, text: string) => {
const clean = text.trim();
if (!clean) return;
draftDb.todos.push({ id: `todo_${Date.now()}`, text: clean, done: false });
return [[EFFECT_IDS.SET_TODOS, current(draftDb.todos)]];
});
// subs.ts
regSub(SUB_IDS.TODOS_LIST, 'todos'); // root sub
regSub(
SUB_IDS.TODOS_OPEN_COUNT, // computed sub from root sub
(todos: Todo[]) => todos.filter((t) => !t.done).length,
() => [[SUB_IDS.TODOS_LIST]]
);
// Usage example (React):
// const todos = useSubscription([SUB_IDS.TODOS_LIST], 'TodoList');
// const openCount = useSubscription([SUB_IDS.TODOS_OPEN_COUNT], 'TodoFooter');
// dispatch([EVENT_IDS.APP_INIT]);
// dispatch([EVENT_IDS.TODOS_ADD, 'Buy milk']);
```