Install
openclaw skills install @demark-pro/effector-ecosystemUse when designing, reviewing, generating, or refactoring React applications built with Effector, effector-react, Farfetched, Atomic Router, @effector/next,...
openclaw skills install @demark-pro/effector-ecosystemUse this skill for frontend applications that use or plan to use:
effector-react for binding units to components@withease/contracts or compatible contracts for runtime validationpatronum, atomic-router, effector-storage, effector-action, effector-forms, @withease/factories, @withease/i18next, @withease/web-api, @effector/next, @effector/reflect, @withease/redux, eslint-plugin-effector, Effector Babel/SWC pluginsThis skill is project-structure agnostic. It may use paths like entities/user or shared/api in examples, but it must not decide Feature-Sliced Design placement rules. When the question is mainly about layers, slices, public APIs, import boundaries, file placement, or project structure in an Effector project, use effector-fsd.
The target architecture:
UI is dumb. Business logic is declarative. Remote data is validated. Startup is explicit and scoped. Remote-operation semantics are modeled, not hidden in React components.
Use this skill when the user asks about:
sample, combine, attach, effects, stores, events, factories, Scope, SSR, scopeBinduseUnit@effector/next, App Router, Pages Router, SSR, hydration, Scope serializationDo not use this skill as the primary source for FSD structure. Use effector-fsd for placement and import-boundary decisions.
When answering, first identify the goal:
If the user gives code, review it against this skill and return:
effector-fsd is also in scopeWhen the user asks for a full audit, deep review, architecture audit, or “is everything best practice?”, do not stop at local anti-patterns. Perform a cross-cutting static data-flow audit. Build and inspect these graphs before writing the final answer:
main/entrypoint → fork/Provider → appStarted → storage/session restore → router/history installation → initial route open. Flag router/history startup that can open protected routes before auth state is known.401/403 API failures, route redirects, protected-route rejection. Flag any sessionCleared/logout/unauthorized path that does not update route state.concurrency, abortAll, cache/update policy, and $pending gates. Flag duplicated starts, unintentional TAKE_EVERY, stale-result races, and page models that refetch while their route is closed.let intervalId, missing scopeBind, and declared appDestroyed/stop events that are never called.sample.fn, combine, store .map, reducers, and contracts. Flag transformations that can throw (new Date(...).toISOString(), JSON.parse, unsafe property access), long inline algorithms, and success handlers that read operation $data instead of the clock payload.Promise, local pending state around Effector events, direct route/API calls, or business branching in JSX.For each finding, include file/path evidence, severity, why it matters, a minimal fix, and an acceptance criterion. If a problem is not directly visible but follows from Effector static graph semantics, explicitly say it is an inferred risk and show the chain of units that creates it.
React components must:
useUnitReact components must not:
sample, createEvent, createStore, createEffect, or factories during render$store.getState()watch for application behavioruseUnit shape per connected componentFor every component that reads/calls several Effector units from the same model, prefer a single useUnit call with an object or array shape:
const { value, submitDisabled, onValueChange, onSubmit } = useUnit($$form);
or:
const [value, submitDisabled, onValueChange, onSubmit] = useUnit([
$value,
$submitDisabled,
valueChanged,
submitted,
]);
Then destructure every returned value and pass the bound callbacks to JSX. Name handler-like values returned from useUnit with React-style on* aliases: expose onSubmit: submitted, onValueChange: valueChanged, onRetryClick: retryClicked. Keep Effector events named as facts in models; alias them only for React binding.
Avoid repeated bindings in the same component:
// bad by default: noisy and easier to desync during refactoring
const value = useUnit($value);
const submitDisabled = useUnit($submitDisabled);
const onValueChange = useUnit(valueChangedEvent);
Split the component when subscription granularity matters instead of scattering many useUnit calls in one component.
Create units at module level only.
Use:
createEvent for facts that happenedcreateStore for statecreateEffect only for side effects not already handled by Farfetchedattach to inject stores/params into an effect declarativelysample as the main connection operatorcombine for view modelssplit or effector-action for complex branching when it improves readabilitycombine, sample.fn, or store .mappatronum for common operators like debounce, throttle, reset, status, pending helpersscopeBind for callbacks that leave Effector's call stack and must still work inside a ScopeAvoid:
watch for logic$store.getState() for production logiccombine, sample.fn, or store .mapundefined from store reducers unless skipVoid: false is intentionalPrefer concern-based submodels over large all-in-one models. When a model grows into several workflows, split it into submodels such as $$form, $$filters, $$list, $$selection, or $$dialog; keep each submodel responsible for its own state and local rules. The top-level model should stay thin and orchestrate interactions between submodels with sample and other declarative connections.
Keep Effector models declarative by extracting non-trivial data transformation to named pure functions. Small boolean checks or simple field joins can stay inline; complex mapping, sorting, grouping, DTO normalization, permission-derived view models, or formatting should live near the owning model/API/domain helper. The model should connect stores/events and call these functions, not hide algorithms inside reactive operators.
Prefer factories over copy-pasted Effector model code for repeated forms, filters, widgets, or other independent instances with the same behavior. In SSR/Scope/SID-sensitive apps, use @withease/factories and configure the Effector Babel/SWC plugin factories field; invoke factories at module top level, never during render.
Use Farfetched for backend communication:
createJsonQuery for data readscreateJsonMutation for data writesdeclareParams<T>() for typed paramsresponse.contract for runtime validationmapData to map DTOs after validationmapError: ({ error }) => ... to normalize transport/validation/domain errors with the current Farfetched object-argument shapeconcurrency operator for route/search/filter cancellation; for submit de-duplication prefer an explicit Effector $pending gate and choose Farfetched concurrency cautiouslycreateBarrier + applyBarrier for auth refresh or unavailable-resource flowskeepFresh, cache, .refresh, and update for refresh/cache semantics when appropriaterequest.fetch.credentials for cookie/session APIs in current Farfetched code@farfetched/atomic-router for query-driven route loading when Atomic Router is usedNever trust backend data without a runtime contract.
Prefer one application event such as appStarted and one page/route event such as pageStarted over several free-floating startup functions.
Default pattern:
const scope = fork();
await allSettled(appStarted, { scope, params: startParams });
Connect storage pickup, i18n, router start, initial queries, and browser integrations from that event with sample or effects. Avoid sequences like this by default:
await startAppClock(scope);
await allSettled(appStarted, { scope });
await startRouter(scope);
Treat that sequence as wrong by default. Prefer modeling adapter installation as effects started from appStarted (sample({ clock: appStarted, target: routerStartedFx })). Keep free-floating startAppClock(scope) / startRouter(scope) only as a documented last-resort host wiring boundary that cannot be expressed as a scoped effect before the first callback fires. Business decisions must still be triggered from appStarted, route events, or scope-bound callbacks.
Use allSettled(scope) only when you intentionally need to wait for already-started async work that was triggered outside the direct allSettled(event, { scope }) call, for example by a scopeBind callback from a timer, SDK, history listener, or WebSocket.
Do not add packages because they are popular. Choose them when they solve a specific architectural problem.
Use the package map in references/01-package-map.md and the detailed notes in references/10-ecosystem-library-notes.md.
Code examples may use path aliases such as:
@/shared/api/base-url
@/entities/session
@/features/profile-update
@/pages/user/model/page.model
These are illustrative. Do not infer full project-structure rules from this skill. For placement and boundary decisions, use effector-fsd.
Stores:
export const $user = createStore<User | null>(null);
export const $isAuthorized = createStore(false);
Events:
export const formSubmitted = createEvent<FormValues>();
export const searchChanged = createEvent<string>();
export const pageOpened = createEvent();
useUnit UI binding shapes should expose these events as handler aliases:
export const $$form = {
value: $value,
onValueChange: valueChanged,
onSubmit: formSubmitted,
};
Effects:
export const analyticsTrackedFx = createEffect<AnalyticsPayload, void>();
Queries/mutations:
export const userQuery = createJsonQuery(...);
export const updateProfileMutation = createJsonMutation(...);
Avoid vague names:
// bad
export const setData = createEvent<any>();
export const update = createEvent();
export const fx = createEffect();
Before giving a detailed answer, consult the relevant files:
references/00-source-policy.mdreferences/01-package-map.mdreferences/03-effector-modeling.mdreferences/04-farfetched-contracts.mdreferences/05-react-ui-binding.mdreferences/06-routing-forms-persistence-i18n.mdreferences/07-testing-tooling.mdreferences/08-anti-patterns.mdreferences/09-review-checklist.mdreferences/10-ecosystem-library-notes.mdreferences/11-nextjs.mdreferences/12-production-audit-playbook.mdBe specific. Prefer concrete code examples.
When correcting code, show the minimal correct version first, then explain.
When placement is the central question, switch to or ask to apply effector-fsd.
When a choice is trade-off based, say what the default should be and when to deviate.