Демо-режим
Пять готовых профилей вместо настоящих данных. Без них проверить арифметику недельных и месячных окон можно только реально прождав месяц.
http://localhost:5173/?mock=1 # профиль «Артём», полный интерфейс
http://localhost:5173/?demo=max # профиль «Максимум», гостевой видПользовательская сторона этой же машинки — «Показать другу»: тот же генератор, та же защита, только вход из настроек.
Два разных признака
| Что значит | Чем включается | |
|---|---|---|
DEMO_MODE | загружена синтетика, запись погашена | любой из способов ниже |
GUEST_MODE | интерфейс подстрижен «для друга»: плашка сверху, из настроек убраны вход, копия, импорт и сброс | только ?demo=, startapp=demo_* и пункт в настройках |
Разделены ради съёмки. Кадры настроек снимаются в демо-данных, но интерфейс на них должен быть полный — иначе документация описывала бы не то приложение, которое видит человек. Поэтому ?mock= даёт данные без плашки.
Профили
| Идентификатор | Имя | Что внутри |
|---|---|---|
m | Артём | Базовый набор, 45 дней, перекос в работу. Три навыка на разных ступенях: самостоятельный с большим капиталом, привязанный к приоритету, едва начатый. На нём снимается документация |
f | Марина | Набор «Семьянин», 240 дней, ровный баланс, пять навыков |
max | Максимум | 13 месяцев без единого пропуска, 10 приоритетов, архив, 12 навыков от «не начато» до 15 000 часов, весь реестр достижений |
burnout | Выгорание | Набор «Восстановление», 120 дней, заряд внизу, ответы о расходе почти каждый день |
start | Первая неделя | 6 дней, один навык, ничего не выдано руками |
?mock=1 читается как m: единица — не идентификатор, а наследство. Работает и ?mock=max, если нужен другой профиль с полным интерфейсом.

Разница с ?demo=max — ровно одна строка сверху. Этот кадр стоит на лендинге постером под живым фреймом и потому снят единственным прогоном не в телефонный экран, а в высоту рамки. Совпасть он обязан во всём: профиль — иначе при подмене поедут числа и число полос, высота — иначе в постер не попадёт панель вкладок, и в момент подмены она «появится».
Генераторы детерминированные: один и тот же прогон даёт одну и ту же картину. Идентификаторы приоритетов и навыков литеральные (p1…pa, k1…kc), а не через newShortId — тот берёт Math.random(), и совпадение прогонов ломалось бы ровно на нём.
Чего в демо-данных нет
- Онбординга. Все профили ставят отметку «пройден», поэтому первый запуск в демо недостижим — его снимают отдельным прогоном без параметров.
- Настоящей сессии. Транспорт объявляет себя ненастроенным, строка «Аккаунт» в настройках не появляется.
Ответы о том, что посадило заряд, и архив приоритетов здесь есть: раздел «Что сажает батарею» на профилях m, max и burnout непустой.
Запись отключена
WARNING
В демо запись отключена целиком: ни клики, ни настройки, ни достижения никуда не уходят. Всё, что вы там накликаете, исчезнет при перезагрузке.
Это защита от единственного по-настоящему опасного сценария: синтетика, уехавшая на сервер поверх настоящей истории. Отката у такого не бывает.
Настройки в демо-режиме честно об этом пишут — приписка под кнопками сброса.
Где это используется
- Ручная проверка окон в 7 и 30 дней.
- Скриншоты для документации: большая часть кадров снята именно так.
- Тест
src/demo/profiles.test.tsследит, чтобы профили оставались валидными и детерминированными.
Рядом
Показать другу · Локальный запуск · Скриншоты · Данные и синхронизация
Как это работает внутри
src/demo/mode.ts— разбор адреса, один раз при загрузке модуля. Только этот файл в каталоге знает проwindow: остальные обязаны собираться в node, иначе тест профилей пришлось бы гонять в браузерной среде.src/demo/generate.ts— общий генератор;src/demo/profiles.ts— сценарии. Профиль это данные, а не код: добавить шестого человека значит дописать литерал. Тот же приём, что у наборов приоритетов.src/demo/random.ts— линейный конгруэнтный генератор. НеMath.random(): одна и та же картина нужна и для глаза, и для скриншотов, и для теста.- Данные считаются относительно текущей даты (
addDays(now, -back)), поэтому при съёмке скриншотов часы замораживаются — иначе даты на кадрах уезжали бы каждый прогон. - Запись гасится на границе хранилища, а не в сторе. Раньше это делали четырнадцать ранних возвратов, по одному на каждую точку записи, и пока стоком был один CloudStorage, приём работал. С журналом операций и сервером каждый новый путь записи стал пятнадцатым местом, которое надо не забыть, а цена промаха выросла с «испорчено облако одного устройства» до «операция разъехалась по всем». Теперь предохранителей три, и они на выходах:
resolveBackend()вstore/local/db.tsотдаётmemoryStore()/memoryOps(),storeвtelegram/cloudStorage.tsне подмешивает облако,BASE_URLвsync/transport.tsпуст. Забытое место больше не течёт наружу — оно остаётся в памяти. - Побочная выгода: под тот же предохранитель попали сессия и идентификатор устройства — они ходят через
metaтого же хранилища. Выход из аккаунта, единственный не прикрытый прежними проверками, из демо до сервера не дотянется. - Ненастроенный транспорт — не заглушка, а уже обкатанная ветка: ею живёт каждый, у кого не задан
VITE_SYNC_URL.