Traces
Трейс — JSON-файл со всем, что нужно, чтобы воспроизвести происходившее в одном scope: состояние до первого шага, каждый внешний вызов и каждый результат эффекта. Вы записываете баг, отправляете файл, а коллега воспроизводит его в своей сборке — эффекты отвечают из записи, двадцать кликов не нужны, — как в отладчике Elm.


Экспорт
Экспорт трейса → Сформировать JSON (или Ctrl+S где угодно, или кнопка экспорта в панели инструментов). Затем Скачать .json или Копировать. Строка под кнопками подводит итог: 7 шагов · 4 результатов эффектов · 5.6 КБ · 2 замаскировано.
Трейс берётся из живого scope и содержит:
| Часть | Содержимое |
|---|---|
format | deffector-trace@1 |
meta | имя приложения, URL, build, версии effector и агента, время, scope, user agent; focus — шаг, который откроется после воспроизведения |
units | все юниты, на которые ссылается трейс: имя, тип, sid, файл |
initial | значения записываемых сторов до первого шага |
steps | внешние вызовы — события из UI, таймеры и т. п. — с их params; правки из панели тоже шаги, поэтому они тоже воспроизводятся |
effectResults | что вернул или выбросил каждый вызов эффекта, по порядку вызовов, с длительностью |
notes | ваши заметки |
redacted | пути, которые были замаскированы |
lossy | значения, которые нельзя восстановить точно (функции, DOM-узлы, экземпляры классов) |
Секреты маскируются с сохранением длины. Значения под ключами, похожими на секреты, — password, passwd, secret, token, authorization, cookie, api_key / apiKey, cvv, card (без учёта регистра, в том числе как часть более длинного ключа), — и строковые значения сторов с такими именами заменяются на • той же длины. Валидация, которая проверяет длину, воспроизводится так же.
Если выбрана транзакция, флажок открыть на #N после воспроизведения записывает её в meta.focus.
Импорт и воспроизведение
Вставьте трейс в текстовое поле или нажмите Открыть файл…, затем Проверить совместимость. deffector сопоставляет юниты трейса с юнитами запущенного приложения:
- сначала по
sid; - если sid сменился (код изменили, другая сборка) — по имени, типу и файлу, с предупреждением;
- ненайденные юниты — ошибки, шаги, которые их вызывают, будут пропущены.
Отчёт также сообщает, если трейс записан на другой сборке (опция build) или другой версии effector, что было замаскировано, сколько значений с потерями и сколько заметок оставил автор.
Дальше:
- Воспроизвести в новом scope — deffector создаёт свежий scope из начального состояния трейса, подменяет хендлеры эффектов записанными результатами и выполняет шаги по одному, так что видно, как они появляются в логе. Баннер пишет «Воспроизведение …: шаг 3 из 7 — результаты эффектов берутся из записи, сеть не трогаем». По окончании новый scope становится живым.
- Применить сразу — то же без пауз; баннер затем пишет «Трейс … применён».
В глобальном режиме (без fork()) воспроизведение записывает начальное состояние на месте и на время воспроизведения подменяет хендлеры эффектов глобально.
Открыть на шаге
Поле Открыть на шаге принимает #step=142 (или просто число). После воспроизведения deffector переходит к транзакции, которая соответствует шагу 142 записи. #step=N, вставленный после JSON в поле импорта, работает так же, а трейс с meta.focus открывается на нём сам. Если такого шага в воспроизведении нет, появится сообщение.
Тест из трейса
Скопировать как тест выдаёт тест для vitest/jest, который воспроизводит трейс без панели:
import { fork, allSettled } from 'effector';
import { suggestFx, incremented, quickFill } from './/src/index';
import { email_changed } from './/src/field';
// Recorded by deffector: deffector demo · React, 2026-10-17T11:23:34.545Z
test('replays deffector demo · React trace', async () => {
const replies = {
suggestFx: [
{ status: 'done', value: ['ann@gmail.com'] },
{ status: 'done', value: [] },
],
};
const reply = (list: { status: string; value: unknown }[]) => () => {
const r = list.shift()!;
return r.status === 'done' ? Promise.resolve(r.value) : Promise.reject(r.value);
};
const scope = fork({
handlers: [[suggestFx, reply(replies.suggestFx)]],
});
await allSettled(incremented, { scope });
await allSettled(email_changed, { scope, params: 'ann@gm' });
// здесь неправильно проверяется пароль
await allSettled(quickFill, { scope });
// expect(scope.getState($store)).toEqual(...)
});Это заготовка (выше в сокращении): импорты выводятся из файлов юнитов, а имена юнитов превращаются в идентификаторы (email.changed → email_changed), поэтому подправьте их под свои экспорты; непустые начальные значения попадают в fork({ values }), записанные результаты эффектов — в fork({ handlers }), каждый шаг становится allSettled, а ваши заметки — комментариями. Добавьте expect, который зафиксирует баг.
Что переносится, а что нет
Трейс воспроизводит входы, а не выходы: шаги заново выполняет ядро принимающей сборки, поэтому исправленный баг действительно не воспроизводится, а изменённая модель показывает новое поведение. Эффекты, которые вызывались в записи, получают записанные результаты по порядку; эффект, который запись ни разу не вызывала (потому что код изменился), выполняет свой настоящий хендлер. Что трейс перенести не может: значения из lossy и вызовы юнитов, которых нет в принимающей сборке.
Трейсы из демо взаимозаменяемы: модель собрана babel-плагином, sid везде одинаковые, и трейс, записанный в React, воспроизводится во Vue, Solid или Next.js.