← все проекты

Контент Завод — новое ядро

Пересборка сервиса с нуля ради стабильности. Стенд newtest.24contentzavod.ru, папка /var/www/cz-core, порт 3003. Прод не трогаем до дня переключения. План: docs/00..08 + ACCEPTANCE.md

repo: /var/www/cz-coredocs: /var/www/cz-core/docs
готовS1 · С0 · Фундамент12/13

Цель: Схема core, реестры состояний и инвариантов, триггеры, сборщик карты, каркас тестов, стенд newtest, гейт выкладки

Стенд и окружение
newtest.24contentzavod.ru, pm2 cz-core, порт 3003, отдельная схема core в существующей Supabase
https://newtest.24contentzavod.ru работает: pm2 cz-core, порт 3160, сертификат выпущен, http→https. Экран «Здоровье» на главной
Приложение поднято: pm2 cz-core, порт 3160, страница 200, база отвечает, nginx настроен. Блокирует только DNS: newtest.24contentzavod.ru указывает на 72.56.74.240, нужен 80.90.184.203 (панель timeweb, доступ у владельца). После смены — выпустить сертификат certbot.
feature
Схема данных с нуля
работы, артефакты, заказы, шаги заказов, кошельки, публикации, планы, журнал, идемпотентность, входящие события
Схема core применена: 21 таблица, 9 перечислений, представление баланса. Миграция db/migrations/0001_core_schema.sql
feature
Реестр состояний и переходов
единственный источник правды, из него собирается карта
Таблица core.work_transitions: 8 разрешённых переходов, единственный источник правды; переход только через core.work_transition()
feature
Инварианты в базе
триггеры, частичные индексы, проверки — по docs/02-ЯДРО.md
Триггеры и функции: запрет прямой записи состояния, неизменяемость артефактов/журнала/кошелька, защита указателя версии, запрет сброса готового шага, hold/settle. db/migrations/0002_invariants.sql
feature
Тесты на нарушение инвариантов
каждый инвариант проверяется попыткой сломать, ожидается отказ базы
26 проверок, каждая пытается сломать правило и ждёт отказа. Все зелёные, включая INV-081 (вторая задача провайдера поверх живой) и INV-074 (указатель на чужой артефакт). db/tests/test-invariants.sql
feature
Сборщик карты + проверки полноты
npm run map падает, если роут, таблица или переход не описаны
tools/build-map.ts: карта из кода и базы, 241 строка. Падает, если переход есть в коде и нет в базе (проверено намеренной поломкой), если роль Клиент видит кухню, если у шага нет цены
первая редакция плана, заменено второй
feature
Выкладка с гейтом
тесты и карта до перезапуска, откат на предыдущую сборку при красном
первая редакция плана, заменено второй
feature
Стенд и окружения
newtest.24contentzavod.ru, pm2 cz-core, порт 3003, схема core, разделение окружений
Стенд newtest.24contentzavod.ru с сертификатом, выкладка через гейт, окружение проверяется целиком перед каждой выкладкой и при старте воркера.
feature
Сборщик карты с проверкой полноты
падает, если роут, таблица или переход не описаны
Карта описывала только данные. Добавлены разделы «Из чего собрано» (модуль + первая строка его описания), «Что охраняется» (все проверки) и состояние базы. Полнота проверяется на выкладке: появился модуль без строки в карте — выкладки нет.
feature
Выкладка с гейтом и откатом
тесты, карта, смоук до перезапуска
tools/deploy.sh: типы, юнит-тесты, инварианты, поведение заказов, карта, миграции. Красный откатывает сборку. Итого 73 проверки
feature
RT-02 · Защита указателя текущей версии
триггер: current_artifact_id ссылается только на артефакт своей работы; меняется при создании версии или явном откате. INV-074
Указатель текущей версии проверялся только на «не пусто». Можно было поставить артефакт чужой работы, откатить на старую версию, обнулить у готовой — последнее ещё и рушило согласование. Все три запрещены базой. 4 проверки.
feature
RT-05 · Мягкое удаление не ломает уникальность
условия уникальности публикаций учитывают удаление работы; восстановление пересматривает состояние. INV-077
Проверено: бренд с тем же названием заводится заново, удалённые работы не попадают в выборки, уникальность заказов держится на ключе идемпотентности каждой работы. 4 проверки.
feature
DNS для стенда
newtest.24contentzavod.ru сейчас ведёт на 72.56.74.240, нужен A-запись на 80.90.184.203 (cfru). Панель timeweb. После смены: certbot --nginx -d newtest.24contentzavod.ru
Владелец вернул A-запись на 80.90.184.203, сертификат выпущен certbot, редирект настроен
chore
готовS2 · С1 · Кошелёк и заказы13/14

Цель: Деньги перестают теряться: двойная запись, холды, идемпотентные заказы, чекпойнты шагов

Кошелёк на двойной записи
баланс как сумма записей, ни одного прямого изменения баланса
Двойная запись: wallet_entries + представление баланса, холд/списание/возврат, сверка. 25 проверок в billing.check.
feature
Функции денег
резерв, списание, возврат, корректировка — только через ядро
core.hold и core.settle в базе; завершитьЗаказ считает списание по готовым шагам, возврат идёт автоматически в той же транзакции
feature
Заказы с ключом идемпотентности
повтор возвращает существующий заказ
src/orders.ts: создатьЗаказ, повтор возвращает тот же заказ. Тест поймал дефект: проверка состояния стояла раньше проверки ключа, двойной клик получал «работа уже собирается»
feature
Единственность активного заказа
частичный уникальный индекс
Уникальный индекс orders_one_active_per_work плюс ключ идемпотентности: повтор возвращает тот же заказ. Проверено в orders.check.
feature
Реестр задач провайдера
запись до ожидания результата
src/providers.ts: 4 провайдера, 12 моделей, у каждой цена и ожидаемая длительность. Таймаут = ожидание × 3 (INV-082). У провалившейся задачи стоимость ноль
feature
Прайс одним справочником
цена считается и показывается до нажатия, плановая цена это потолок
первая редакция плана, заменено второй
feature
Тесты на гонки денег
20 параллельных запросов, параллельные резервы
tools/orders.check.ts 19 проверок + tools/settle.check.ts 23 проверки, оба в гейте выкладки
feature
Шаги заказа с чекпойнтами
готовый шаг не выполняется и не оплачивается заново
core.order_steps + шагГотов/шагНеВышел/шагПропущен. Готовый шаг нельзя сбросить (триггер). Проверено: 3 шага из 5 → списано 252 ₽ вместо 272 ₽
feature
Идемпотентность запросов API
заголовок Idempotency-Key, сохранённый ответ на повтор
src/idempotency.ts: ключ + хэш тела, повтор возвращает сохранённый ответ, подмена тела отклоняется, упавшее действие освобождает ключ. 10 параллельных запросов выполнили действие один раз
feature
Прайс справочником
цена до нажатия, плановая цена это потолок
src/prices.ts: цена работы = сумма цен шагов, 6 линий. ценаЗаСделанное() для частичного результата. 12 юнит-тестов
feature
RT-01 · Нормализация ключа идемпотентности
хэш задания по нормализованному содержанию: пробелы, регистр, порядок полей, пустые значения. INV-073
src/brief.ts: хэш по нормализованному заданию (пробелы, регистр, ё/е, порядок полей, пустые значения). 13 юнит-тестов
feature
RT-07 · Массовая постановка одним резервом
план из 14 работ: один резерв на пачку, блокировка кошелька берётся один раз
src/orders.ts: создатьЗаказыПачкой через точки отката. Проверено: денег на 3 из 5 — три заказа созданы, две работы помечены пропущенными, постановка не обрушилась
feature
RT-08 · Отсечки баланса
ежемесячная запись-итог, от которой считается остаток; включается по метрике времени ответа
Отсечки в работах, а не в рублях: три работы — важно, ноль — срочно. Формат берётся тот, что бренд делает на самом деле. Тревога закрывается сама, когда деньги вернулись. 16 проверок.
feature
RT-09 · Потолок доведения в деньгах
фактические затраты по заказу не превышают плановую цену больше чем на согласованный процент; ждёт ответа на В-21
За оплаченный результат второй раз не платим (правило владельца). Потолок по шагу и равен его цене — обычно одна оплаченная попытка. Провальные попытки бесплатны и повторяются. 14 проверок.
feature
готовS3 · С2 · Очередь и воркер9/12

Цель: Работа переживает сбои: Graphile Worker в той же базе, аренда, повторы, приоритеты

Захват заказа через skip locked
два воркера не берут один заказ
первая редакция плана, заменено второй
feature
Аренда с продлением
умер воркер — заказ подхватывается с последнего шага
Graphile Worker: воркер pm2 cz-worker, мягкая остановка по SIGTERM (доделывает шаг), брошенное подбирается автоматически
feature
Повторы с отступом и потолком
1, 5, 15 минут; после потолка честный провал с причиной по-русски
первая редакция плана, заменено второй
feature
Отмена человеком
воркер останавливается на ближайшем шаге, холд снимается
первая редакция плана, заменено второй
feature
Частичный результат не пропадает
оплаченные куски переиспользуются при повторе
Готовый шаг не переделывается ни при повторе задачи, ни при перезапуске. Проверено: повтор сборки не создал второй артефакт
первая редакция плана, заменено второй
feature
Хаос-проверки
убить воркер, оборвать сеть, перезапустить сервер, два воркера
tools/chaos.check.ts с настоящим воркером: перезапуск посреди сборки (заказ доведён, шаги не переделаны, холд один, артефакт не задвоился), десять заказов одновременно (10 из 10, холдов 10, артефактов 10), баланс равен сумме записей
feature
Очередь в той же транзакции, что и заказ
Graphile Worker на Postgres, без второго хранилища
Graphile Worker, схема graphile_worker. Постановка через graphile_worker.add_job внутри транзакции заказа, job_key = заказ (повтор не плодит задачи)
feature
Повторы с отступом и разбросом
потолок попыток, честный провал с русской причиной
src/ladder.ts: лестница доведения — повтор (1/5/15 мин) → уточнение задания → запасная модель → пропуск необязательного → сдаёмся. 11 юнит-тестов, лестница конечна
feature
Приоритеты и лимиты на бренд
один бренд не забивает очередь остальным
Приоритет в постановке задачи есть; лимит одновременных на бренд — в С3 вместе с провайдерами
feature
Бюджет времени на заказ
превышен — принудительный провал с возвратом
Срок ставился, но никем не проверялся: застрявший заказ держал деньги в резерве бесконечно. Теперь закрывается по сделанному, с запасом 10 минут, отметкой «закрыт по сроку» в артефакте и тревогой владельцу. Ночная сверка делает это до сверки денег.
feature
Отмена и мягкая остановка при выкладке
заказы не рвутся на полушаге
src/cancel.ts: отмена считает за сделанное, работа возвращается в «задание», воркер видит отмену между шагами и не рвёт оплаченный вызов
feature
RT-13 · Сквозной идентификатор в фоновой работе
идентификатор — обязательное поле заказа и задачи, а не контекст выполнения. INV-079
След идёт от создания заказа через очередь и сборку до расчёта и артефакта.
feature
готовS4 · С3 · Внешние службы12/17

Цель: Провайдеры, вебхуки, деградация: снаружи может лечь что угодно, мы не теряем ни денег, ни работ

Профиль бренда одним документом
товар, аудитория, голос, оформление, контакты, сети
src/brand-profile.ts: схема на zod, версионирование (заказ работает по версии на момент создания), контекст для модели, проверка готового текста на стоп-слова и запреты с учётом склонений
первая редакция плана
feature
Линия: пост
дизайнер, копирайтер, критик, референсы — дословно
первая редакция плана
feature
Линия: карусель
hook-maker, copywriter, designer, critic, assemble + банк хуков и 835 референсов
Пайплайн перенесён дословно (сверка хэшей промтов в гейте), подключён к ядру: профиль нужной версии, банки 1161 хук и 835 образцов с ротацией, запреты бренда останавливают шаг, каждый вызов модели записан на заказ и на шаг
первая редакция плана
feature
Линия: короткое видео
сценарий, кадры, склейка, субтитры, музыка
первая редакция плана
feature
Линия: аватар
сцена, озвучка, OmniHuman, сборка, проверка речи
первая редакция плана
feature
Линия: ролс
шаблоны и рендер
первая редакция плана
feature
Нормализация к канону на приёме
один формат у всех файлов, склейка не падает
src/media.ts: канон по форматам, приведение до укладки в хранилище. Инцидент 08.08 воспроизведён на настоящих файлах (704x1248@25 + 720x1280@24) — склейка разнобоя отклоняется внятно, после приёма всё в каноне и собирается
первая редакция плана
feature
Приёмка артефакта
формат, целостность, речь, надписи, запреты бренда
src/acceptance.ts: формат, звуковая дорожка, длительность с допуском, запреты бренда, речь через расшифровку (порог 60% с учётом падежей), надписи критиком (латиница, запреты на картинке). Дорогие проверки не зовутся, если дешёвые уже нашли жёсткий брак
первая редакция плана
feature
Сверка качества с продом
проверочные задания, оценка не ниже текущей
первая редакция плана
feature
Единая обёртка вызовов наружу
таймауты, повторы, ограничение скорости, предохранитель, журнал
src/call.ts: таймауты из реестра моделей, предохранитель, запись задачи ДО ожидания, стоимость только за успешные. 16 проверок
feature
Реестр провайдеров
модели, цены, лимиты, чем заменить — из него же прайс и карта
src/providers.ts: 4 провайдера, 12 моделей, у каждой цена, ожидаемая длительность и запасная модель
feature
Долгие задачи: вебхук плюс опрос
кто первым принёс результат, тот закрывает шаг
Кто первым принёс результат, тот и закрыл; второй ничего не меняет. Проверено: вебхук после опроса не переписал результат и не задвоил стоимость
feature
Входящие события
подписи, дедупликация по идентификатору, отложенный разбор
src/webhooks.ts + /api/hooks/[источник]: подпись сравнением постоянного времени, дедупликация уникальным индексом, отклонённое тоже записывается. Проверено вживую: верная подпись 200, подделка 401, неизвестный источник 404
feature
Записанные ответы провайдеров
тесты и стенд без реальных трат
Хранится форма ответа (поля и типы), а не сам ответ: значения разные каждый раз, а внутри них чужие ссылки. Первый успешный ответ модели становится эталоном автоматически.
feature
Экономный режим стенда
реальные вызовы только по явному флагу
CZ_FAKE_EXECUTORS — пробные исполнители без обращений к провайдерам, CZ_SPEEDUP — сжатие ожиданий. На них работают проверки сборки и хаоса.
feature
RT-04 · Предохранитель провайдера раньше остановки заводов
счётчик отказов по заводу и по направлению провайдера; порог провайдера ниже. INV-076
core.provider_health + provider_failed/provider_ok: счётчик общий для всех воркеров, живёт в базе. После серии отказов направление закрыто, очередь ждёт
feature
RT-15 · Еженедельная сверка записанных ответов
прогон против реальных провайдеров на минимальных настройках, обновление записей
По понедельникам форма свежих ответов сверяется с эталоном на уже записанных данных — к провайдерам не ходим, стоит ноль. Пропавшее поле или смена типа поднимают тревогу с указанием, что чинить. 14 проверок.
feature
готовS5 · С4 · Арендаторы и права10/17

Цель: Организации, роли, вход под клиентом, изоляция данных и файлов

Согласование как признак
никогда не означает «производи»
первая редакция плана
feature
Публикации с версией артефакта
выходит ровно то, что согласовали
первая редакция плана
feature
Требования сетей до отправки
не проходит по длительности или весу — говорим заранее
первая редакция плана, заменено второй
feature
Режимы бренда
ручной, авто без кнопки согласования, пауза
первая редакция плана
feature
Защиты автоматического режима
суточный и месячный потолок, остановка при трёх провалах, уведомление
первая редакция плана, заменено второй
feature
Планы и перевод пунктов в заказы
план на период, запуск по дню
первая редакция плана
feature
Проверка: согласование не тратит денег
20 согласований подряд дают ноль заказов и ноль списаний
первая редакция плана, заменено второй
feature
Организации, бренды, участники, роли
владелец, управляющий, исполнитель, наблюдатель, клиент бренда
core.orgs, org_members с правами списком действий, brands, factories. Роль = набор прав, настраивается
feature
RLS на всех таблицах
плюс проверка в прикладном слое, обе обязательны
26 из 26 таблиц под политиками + роль cz_app без обхода RLS. Экран «Здоровье» следит: таблиц без защиты 0, роль не обходит
feature
Белые списки полей для клиента
себестоимость, наценка, чужие бренды не видны
Закрыто политикой на provider_tasks: себестоимость недоступна клиенту в самой базе
feature
Вход под клиентом
отдельный режим, запрет трат, пометка каждого действия в журнале
core.impersonations + начатьПросмотр/завершитьПросмотр: нужна внятная причина, срок 2 часа, в журнале видно обоих участников, траты запрещены (проверено созданием заказа)
feature
Приглашения и отзыв доступа
одноразовые, со сроком; отзыв мгновенный
В базе хэш секрета, а не сам секрет; одно живое приглашение на адрес; чужой адрес, повтор и просрочка отклоняются; отзыв гасит и живые приглашения
feature
Изоляция файлов
пути с брендом, подписанные ссылки со сроком
Путь содержит организацию, бренд, работу и версию; чужая ссылка и путь не нашей формы отклоняются базой (INV-083); RLS на файлах через владельца.
feature
Матрица проверок доступа
роль × действие × объект, чужое недоступно во всех случаях
tools/access.check.ts: 19 проверок — списки, прямое обращение по id, изменение и удаление чужого, кухня у клиента, деньги, отзыв доступа
feature
RT-06 · Отзыв доступа рвёт подписки
подписка привязана к сессии и проверяет права на каждом событии. INV-078
Подписанную ссылку отозвать нельзя — единственное ограничение это срок. Человеческие ссылки живут 15 минут, поэтому отобранный доступ перестаёт работать через четверть часа. Решается тем же механизмом, что RT-11.
feature
RT-12 · Представления для роли клиента
себестоимость и наценка недоступны на уровне базы, а не только в коде. INV-080
Задачи провайдеров закрыты политикой по праву «видеть_кухню»: клиент их не видит на уровне базы, а не только в коде
feature
RT-11 · Политика сроков подписанных ссылок
короткий срок и одноразовость для чувствительного, час для обычного контента
Срок задаётся назначением, а не числом: показать 15 мин, скачать 5 мин, провайдеру 6 ч. У каждого записано обоснование, есть жёсткий потолок. 7 проверок.
feature
готовS6 · С5 · Деньги трёх уровней9/17

Цель: Агентство, клиент, самостоятельный: плательщик, наценка, тарифы, платежи, сверка, потолки

Работы
списки по наличию артефакта, лёгкие превью, до секунды на 5000 работ
первая редакция плана
feature
Публикации
действие совпадает с надписью, невозможное не показывается
первая редакция плана
feature
Создание контента
холсты форматов поверх заказов
первая редакция плана
feature
План и календарь
план недели, перенос в заказы
первая редакция плана
feature
Настройки бренда
профиль, режим, потолки, сети, часовой пояс
первая редакция плана
feature
Журнал работы для человека
кто, что, когда, сколько стоило
первая редакция плана
feature
Мобильная вёрстка
ширина 390, ничего не уезжает
первая редакция плана
feature
Смоук в браузере
ключевые сценарии на двух ширинах, гейт выкладки
первая редакция плана
feature
Режимы платежа бренда
за счёт агентства, свой кошелёк, свой кошелёк с наценкой
agency/сам/сам_с_наценкой; проверено что за клиента на содержании платит агентство, а клиент со своим кошельком платит сам
feature
Плательщик фиксируется в заказе
смена режима на полпути не уводит списание в другой кошелёк
Смена режима не переписала плательщика уже созданного заказа (INV-037)
feature
Тарифы и подписки отдельно от кошелька
акция «до сентября» это тариф с датой, а не условие в коде
core.tariffs + subscriptions, лимиты настройкой. Акция «до сентября» — тариф с датой, а не условие в коде. Одна активная подписка на организацию
feature
Приём платежей идемпотентно
ЮKassa, Точка, промокоды, ручное начисление
core.credit_payment: пять повторных уведомлений зачислили деньги один раз. Возврат платежа банком снимает сумму даже если она потрачена — единственный разрешённый минус
feature
Партнёрские начисления и выплаты
ОТЛОЖЕНО решением владельца 10.08: замысел — реферальная программа, люди приводят клиентов, мы платим им процент с продаж. Таких кейсов пока нет, поэтому механику не делаем: непонятно, процент с оборота или с нашей маржи, бессрочно или первые N месяцев. Что уже готово в базе (миграция 0025, лежит без дела): виды записей «начисление» и «выплата», кошелёк организации, представление core.partner_balance (начислено минус выплачено). Модуль src/partners.ts удалён намеренно — недоделанный код, который выглядит рабочим, опаснее его отсутствия. ВАЖНО не перепутать с агентской наценкой: в режиме «агентство» кошелёк общий, агентство платит нам за работы своих клиентов, а наценку берёт с клиента напрямую мимо нас. Там начислять нечего.
Проверено 17.08: программа в рабочем сервисе ПУСТАЯ. Ноль партнёров, ноль приведённых, ноль начислений, ноль выплат, ни у кого нет партнёрского баланса. Переносить нечего и никому мы ничего не должны. Возможность в проде есть, но ей не пользовались — если понадобится, делать с нуля под нынешние правила, а не тащить неиспользованное.
feature
Себестоимость и маржа
сколько заплатил клиент, сколько стоило нам, разница по брендам
маржаПоБрендам: выручка минус затраты провайдерам по каждому бренду
feature
Ежедневная сверка денег
кошельки, заказы, провайдеры, платёжки; расхождение поднимает тревогу
core.reconcile: три проверки (баланс=сумма записей, списания=стоимость артефактов, себестоимость<=выручка). Крон 3:17, молчит когда сошлось
feature
Потолки и рубильник
сутки, месяц, одновременные заказы, порог подтверждения, общий стоп
Суточный потолок трат и выпуска у бренда, пауза с запретом в базе, общий рубильник приёма заказов, остановка завода после трёх провалов подряд.
feature
RT-03 · Цена фиксируется в пункте плана
смета фиксирует цену при одобрении; изменение прайса помечает план «смета устарела». INV-075
Утверждение плана записывает цену каждого пункта; заказ берёт её, а не текущий прайс. Расхождение с прайсом видно отдельно — это повод показать новую смету, а не списать разницу. 5 проверок.
feature
В РАБОТЕS7 · С6 · Производство и качество13/22

Цель: Линии контента поверх ядра, перенос пайплайнов дословно, канон формата, приёмка артефакта

Инвентарь 115 таблиц
переносится, не переносится, почему
первая редакция плана
feature
Перенос организаций, брендов, людей
вместе с правами
первая редакция плана, заменено второй
feature
Перенос готовых работ и файлов
в артефакты, с проверкой открываемости
первая редакция плана
feature
Перенос балансов и истории трат
копейка в копейку
первая редакция плана
feature
Перенос банков качества
референсы, вирусные шаблоны, словарь произношения, шаблоны ролсов
первая редакция плана, заменено второй
feature
Сверка после переноса
числа сходятся, отчёт по остаткам
первая редакция плана
feature
Версия пайплайна в артефакте
через месяц понятно, чем именно сделана работа
Отпечаток считается с файлов линии (промты, прайс, канон) плюс модели, которые реально сработали, и шаги с повторами. Строку руками больше не пишем — её забыли бы поменять. 3 проверки.
feature
Профиль бренда одним документом
товар, аудитория, голос, оформление, контакты, сети
src/brand-profile.ts: схема на zod, версионирование (заказ работает по версии на момент создания), контекст для модели, проверка готового текста на стоп-слова и запреты с учётом склонений
feature
Линия: пост
дизайнер, копирайтер, критик, референсы — дословно
Шаги замысел/текст/картинка/приёмка поверх ядра. Пайплайн тот же, что у карусели, с одним слайдом — как в рабочем сервисе. Подпись пишет отдельный копирайтер (gpt-4o-mini через OpenRouter), промт перенесён дословно и сверяется по хэшу в гейте. 14 проверок: шаги совпадают с прайсом, разметка ответа не уезжает в подпись, подпись не подменяется подписью из плана картинки, стоп-слова останавливают шаг, подпись оплачена отдельным шагом.
feature
Линия: карусель
hook-maker, copywriter, designer, critic, assemble + банк хуков и 835 референсов
Пайплайн перенесён дословно (сверка хэшей промтов в гейте), подключён к ядру: профиль нужной версии, банки 1161 хук и 835 образцов с ротацией, запреты бренда останавливают шаг, каждый вызов модели записан на заказ и на шаг
feature
Линия: короткое видео
сценарий, кадры, склейка, субтитры, музыка
Семь шагов поверх ядра: сценарий, кадры, сцены, озвучка, музыка (необязательная), сборка, приёмка. Агенты перенесены дословно скриптами и сверяются повторным переносом. Два банка переехали с данными: примеры сценариев и словарь произношения. Оплаченный кусок укладывается сразу, у каждой сцены своя защита от повторной оплаты, запреты бренда проверяются до дорогой съёмки. 17 проверок.
feature
Линия: аватар
сцена, озвучка, OmniHuman, сборка, проверка речи
Шесть шагов: сценарий, сцена, озвучка, аватар, сборка, приёмка. Оживление кадра это 92% цены работы, поэтому стоит последним — до него весь дешёвый материал и все проверки запретов. Упало раньше — человек платит 34 руб, а не 424. У каждой реплики своя часть шага. 16 проверок.
feature
Линия: ролс
шаблоны и рендер
Пять шагов со своей службой рендера на Remotion. Недоступность службы даёт честный отказ шага, а не потерю работы: кадры и голос уже уложены, повтор соберёт из готового. Приёмка сверяет длину с раскадровкой. 10 проверок.
feature
Нормализация к канону на приёме
один формат у всех файлов, склейка не падает
src/media.ts: канон по форматам, приведение до укладки в хранилище. Инцидент 08.08 воспроизведён на настоящих файлах (704x1248@25 + 720x1280@24) — склейка разнобоя отклоняется внятно, после приёма всё в каноне и собирается
feature
Приёмка артефакта
формат, целостность, речь, надписи, запреты бренда
src/acceptance.ts: формат, звуковая дорожка, длительность с допуском, запреты бренда, речь через расшифровку (порог 60% с учётом падежей), надписи критиком (латиница, запреты на картинке). Дорогие проверки не зовутся, если дешёвые уже нашли жёсткий брак
feature
Сверка качества с продом
проверочные задания, оценка не ниже текущей
feature
RT-10 · Версии профиля хранятся разницей
полный снимок раз в N версий либо хэш профиля вместо копии
Пока храним полные версии: профиль небольшой, разница усложнила бы чтение. Пересмотреть, если версий станет много
feature
RT-14 · Варианты работы вместо второго заказа
кнопка «сделать вариант» создаёт копию работы, а не обходит правило одного активного заказа
Вариант это отдельная работа со своим заказом и оплатой, привязанная к исходной идее. Правило одного живого заказа не тронуто. Цепочек нет, потолок 5. 12 проверок.
feature
Запись pm2 для сборщика ведёт обёртку, а не процесс
Воркер запущен как `bash -c npx tsx src/worker.ts`. pm2 при перезапуске гасит обёртку, node под ней переходит к init и продолжает разбирать ту же очередь. 14.08 так накопилось шесть сборщиков; пробный перехватил живой заказ и «провалил» его объяснением, не имеющим к нему отношения. Пока лечится уборкой в деплое (pgrep -P 1 -f src/worker.ts) и проверкой «сборщик один и боевой». Правильно — пересоздать запись, чтобы pm2 вёл сам node: pm2 delete cz-worker pm2 start node_modules/.bin/tsx --name cz-worker -- src/worker.ts Проверка после: ps -eo pid,ppid,args | grep src/worker.ts — сирот с ppid=1 быть не должно.
fix
Решение: пересобирать ли замысел после отказа критика
Критик забраковал хук на обоих живых прогонах («шаблон из 2018-го»). Сейчас работа отдаётся с пометкой «приёмка не пройдена» — видно в карточке, артефакт помечен неполным. Переспрашивать критика перестали: у шага появился признак «суждение», на неизменившемся входе ответ тот же. Развилка для владельца: пересобирать замысел с замечаниями критика (лестница это умеет, ступень «уточнение») или оставить как есть. Цена круга — примерно 20 ₽ (замысел 3 ₽ + картинка 17 ₽). Нужен потолок кругов, иначе плохой хук будет перерисовываться бесконечно.
Решать оказалось нечего: посмотрел рабочий сервис — там критик судит план ДО отрисовки и переделывает хук, копирайт и дизайн с его замечаниями, до двух кругов (MAX_FIX_ROUNDS=2), и только потом рисует. В ядре критик стоял последним шагом, после картинки, — отсюда и «круг стоит 20 ₽». Порядок повторён: узел src/lines/carousel/plan.ts, подключён в линии поста и карусели. Приёмка осталась, но перестала звать модель: проверяет запреты бренда кодом. Круг правок теперь текстовый, несколько рублей.
decision
В себестоимость пишется наша цена, а не расход у провайдера
src/call.ts: `const стоимость = модель.цена` — в provider_tasks.cost ложится цена для клиента из реестра, а не то, что мы заплатили Kie или OpenRouter. Значит сверка «себестоимость не выше выручки» сравнивает цену с ценой и ничего не проверяет, а маржа по брендам считается неправдой. Что нужно: в реестре моделей поле себестоимости отдельно от цены (у Kie берётся из каталога: gpt-image-2 1K $0.03 / 2K $0.05 / 4K $0.08, nano-banana-pro $0.09), для текстовых моделей — расчёт по токенам из ответа OpenRouter (usage приходит, рабочий сервис его пишет). Плюс курс ₽/$ одним местом. Связано с задачей про единицу тарификации: обе про то, что деньги считаются по выдуманным числам.
Сделано 16.08. У каждой модели в реестре появился расход с единицей (картинка с ценой по крупности, секунда, ролик, запрос, токены), курс доллара — настройка CZ_USD_RATE. Текст считается по токенам из ответа OpenRouter, картинки и ролики — по факту. Проверка tools/cost.check.ts в гейте, 28 утверждений, включая «расход не равен цене».
fix
Видео: сравнить Veo 3.1 Lite с wan 2.7 по цене и качеству
Владелец: Veo 3.1 Lite — 15 ₽ за ролик. В каталоге Kie: veo 3.1 reference-to-video Lite 720p $0.15 за РОЛИК, 1080p $0.175. Наша линия видео сейчас на wan/2-7-image-to-video — $0.08 ЗА СЕКУНДУ, то есть пятнадцатисекундный ролик около 114 ₽ против 15. Разница семикратная. Нужно сравнить качество на одном сюжете и решить, что ставить основой. Спецификация Veo 3.1 есть в памяти проекта (veo31-api-spec).
decision
готовS8 · С7 · Публикация и режимы8/13

Цель: Согласование как признак, публикация с версией, токены сетей, режимы и часовые пояса

Нагрузка
500 заказов, 5000 работ в списках, 50 пользователей
первая редакция плана
feature
Аудит доступа
ноль успешных попыток достать чужое
первая редакция плана
feature
Сверка денег с провайдерами
суммарные траты сходятся
первая редакция плана, заменено второй
feature
Прогон сценариев
глазами СММщика и предпринимателя
первая редакция плана
feature
День переключения
домен, расписания, план отката, откат по кнопке
первая редакция плана
feature
Реестр сетей и проверка до отправки
длительность, вес, соотношение, длина текста, частота
Пять сетей с подписанными пределами: длительность, вес, соотношение, длина текста, хэштеги, суточный потолок. Проверка отдаёт все замечания сразу и различает отказ (сеть не примет) и замечание (примет, но срежет охват). 17 проверок.
feature
Токены сетей
обновление заранее, честная остановка и уведомление при отказе
Истечение доступа видно за неделю, а не в момент отказа площадки. Три отказа подряд = потеря доступа, публикации в такой аккаунт база не принимает. Восстановление обнуляет счётчик и закрывает тревоги. 20 проверок.
feature
Защиты автомата
потолки, остановка при трёх провалах, уведомление владельцу
Потолки трат и выпуска, пауза с запретом в базе, остановка завода после трёх провалов, тревоги с действием и без спама повторами. Канал доставки: Лия в Телеграм через прокси (прямой доступ к api.telegram.org с cfru закрыт). Правила против спама: одна тревога один раз, срочное сразу, важное не чаще раза в час, внимание — сводкой в 10:00, ночью тихо, одно сообщение списком. Крон каждые 5 мин. 18 проверок.
feature
Часовые пояса и пропущенные слоты
зона бренда, правило догона, разведение конфликтов
Время принадлежит бренду: смещение спрашивается у зоны на нужную дату, перевод часов учитывается. Опоздание до 3 ч догоняем, больше — переносим на тот же час следующего дня. Столкновения разводим только вперёд. Ночью не публикуем, ночь считается по зоне бренда. 21 проверка.
feature
Согласование как признак
никогда не означает «производи»
Согласование не создаёт заказов и не меняет состояние работы — проверка это фиксирует числом заказов до и после. В производство ведёт только заказ.
feature
Публикации с версией артефакта
выходит ровно то, что согласовали
Согласование запоминает версию артефакта; пересборка снимает согласование; подменить версию в поставленной публикации база не даёт. В списке публикаций видно, осталась ли выпущенная версия согласованной. 14 проверок.
feature
Режимы бренда
ручной, авто без кнопки согласования, пауза
Ручной, авто, пауза. Авто это не ручной без подтверждения: согласования там нет как этапа. С паузы наружу ничего не уходит (запрет в базе), встать на паузу без причины нельзя. Суточные потолки трат и выпуска считаются по фактам, а не счётчиком. 6 проверок.
feature
Планы и перевод пунктов в заказы
план на период, запуск по дню
Пункт становится работой в свой день, а не при утверждении плана. Смета видна до согласия. Запуск по дню, просроченное больше суток — отдельной тревогой. Нехватка денег на пятый пункт не рушит первые четыре. Пауза и остановленный завод план не запускают. 15 проверок.
feature
готовS9 · С8 · Интерфейс20/20

Цель: СММ-Центр на новом ядре: ничего не пропадает, состояние правдивое, телефон сразу

Состояния подпиской, а не опросом
карточка не может показывать не то, что в базе
SSE-поток /live: состояния работ приходят подпиской через LISTEN/NOTIFY. Проверено смоуком: поток отдаётся как поток, с запретом буферизации nginx, без входа не отдаётся.
feature
Черновики и безопасный уход со страницы
ввод сохраняется на сервере
core.drafts + автосохранение: уход со страницы не теряет заполненное. Проверка tools/screens.check.ts.
feature
Работы
списки по наличию артефакта, лёгкие превью, до секунды на 5000 работ
Раздел работ: список, карточка, версии, файлы, повтор. Смоук проходит по каждому разделу из РАЗДЕЛЫ, поломки видны.
feature
Публикации
действие совпадает с надписью, невозможное не показывается
Раздел публикаций: подключение сетей, очередь отправки, состояния запланирована/отправляется/вышла/ошибка/отменена.
feature
Создание контента
холсты форматов поверх заказов
Экран создания: форматы, пресеты, свои картинки, соотношения. Поля формы латиницей (кириллица терялась в server actions).
feature
План и календарь
план недели, перенос в заказы
core.plans + plan_items, календарь с переводом пунктов в заказы.
feature
Настройки бренда
профиль, режим, потолки, сети, часовой пояс
Профиль бренда, голос, запреты, палитра, логотип, фото товара.
feature
Журнал работы для человека
кто, что, когда, сколько стоило
Лента событий работы человеческим языком, без внутренней кухни.
feature
Мобильная вёрстка
ширина 390, ничего не уезжает
Нижнее меню из основного, остальное в бургере без дублей. Смоук проверяет ширину под устройство и подключённое оформление.
feature
Смоук в браузере
ключевые сценарии на двух ширинах, гейт выкладки
tools/smoke-web.check.ts: 49 проверок по живым страницам, включая чужой бренд 404 и админку только своим.
feature
Публикации: подключение сетей и отправка
Сейчас работу можно сделать, но нельзя выпустить — главный сценарий разорван. Нужны: подключение соцсетей (social_accounts), отправка готовой работы, расписание, повтор при отказе. На проде это самый большой раздел, 881 строка.
Ядро: src/publish.ts — подключение сетей, очередь с арендой (два отправщика не выпустят пост дважды), пределы сети до отправки, 3 попытки и тревога, ВК посты/клипы как на проде. Транспорт ПЕРЕНЕСЁН дословно (1590 строк client.ts), гейт сверяет перенос с оригиналом. Экран: подключение и отключение сетей прямо в разделе, «Готово к выпуску» с выбором аккаунта и времени (пусто = сейчас), сорвавшиеся публикации подняты наверх. Крон раз в минуту (tools/send-publications.ts): пост на 10:00 выходит в 10:00. Отдельно от воркера — сборка может идти час, а расписание это обещание клиенту. tools/publish.check.ts — 23 проверки, наружу не ходит. Прямые каналы Телеграма и MAX пока отвечают честным отказом, переносятся отдельно.
feature
Согласование с экрана
approval.ts в ядре есть, у роли «Клиент» есть право «согласовывать», экрана нет. Клиент смотрит готовое и одобряет до выхода в сеть.
Кнопки на карточке работы, сразу под результатом: решение принимают глядя на файл, а не на журнал. Отдельного экрана согласования нет нарочно — он заставил бы искать работу второй раз. В ядро добавлен возврат на доработку с замечанием (вернутьНаДоработку): снимает согласование и пишет событие. Замечание читает пересборка — человеку не приходится повторять дважды. Вернуть молча нельзя: без замечания пересборка даст то же самое. Согласованная работа показывает «Согласовано …, можно отправлять»; несогласованная — что пока никуда не выйдет. Право «согласовывать» есть у владельца, управляющего и клиента. tools/approval.check.ts вырос с 14 до 18 проверок.
feature
Права и настройки: участники и роли
people.ts в ядре есть (приглашения, роли, вход под клиентом, аудит), экрана нет. Без него в кабинет нельзя позвать второго человека. На проде 332 строки.
Экран: кто в кабинете с настоящими правами (из членства, а не выведенными из названия роли — расхождение уже приводило к кнопкам, которых база не даёт), кого позвали и ещё ждут, приглашение с выбором роли и пояснением что она даёт, отзыв доступа и отзыв приглашения. В ядро добавлены списки: участники(), приглашения(), отозватьПриглашение(). Пригласить, отозвать и вход под клиентом были с самого начала — не было только «показать, кто есть», из-за этого экрана и не было. Ссылка-приглашение показывается один раз: в базе только отпечаток, утечка базы не даёт входа. Себя убрать нельзя. Убрали человека — вход умирает в ту же секунду (триггер на org_members). Гейт поймал мою ошибку: кириллица в именах параметров адреса (?ссылка=, ?кому=). Исправлено, проверено 231 имя.
feature
Создать контент до полного объёма
Пресеты, свои файлы, предпросмотр до оплаты, режимы. На проде это 10 файлов — самый большой раздел после публикаций.
Заготовки (core.presets + src/presets.ts): сохранённое задание с именем — то, что человек уже вводил и что сработало. Одно имя = одна заготовка (второе сохранение обновляет, а не плодит двойников). Счётчик применений, часто используемые идут первыми. К формату предлагаются только его заготовки. tools/presets.check.ts — 12 проверок. Свои картинки как образец: выбираются прямо в форме, уходят в задание полем «образец» — линия рисует по ним, а не выдумывает. Цена и число шагов показываются до нажатия (было раньше), при нехватке денег кнопка объясняет сколько не хватает. Подстановка заготовки идёт через черновик, поэтому переживает обновление страницы. Не делал: предпросмотр результата до оплаты — его нельзя сделать честно, не потратив денег на генерацию. Показываем то, что можно показать заранее: цену, шаги, что уйдёт в модель.
feature
Контент-план отдельным разделом
План как список идей с правкой, отдельно от календаря. На проде 347 строк.
Список выпусков с темами, отдельно от календаря: календарь отвечает на «что на этой неделе», а здесь работают с содержанием. Правка темы и дня, пропуск с причиной, возврат в план, утверждение со сметой и снятие утверждения. В ядро добавлены пунктыПлана, поправитьПункт, пропустить, вернутьПункт, снятьУтверждение. Правится только неутверждённое и незапущенное: утверждённый план это согласованная смета, менять её молча значит менять сумму после согласия. Сверху предупреждение, сколько выпусков без темы — их запускать нечем.
feature
Бриф и настройки компании до полного объёма
ЧАСТИЧНО: загрузка файлов сделана — это и мешало закрыть задачу. core.uploads (отдельная полка: логотип и фото товара не артефакты, их не производил сервис), src/uploads.ts, приём через форму в настройках компании. Принимаем только картинки до 10 МБ, тип и размер проверяются ДО укладки. Имя в хранилище наше и латиницей, имя человека сохраняется как есть. Убрали из списка — файл в хранилище остаётся: он мог уйти в задание работы. tools/uploads.check.ts — 12 проверок на настоящих байтах. ОСТАЛОСЬ: палитра, водяной знак, особенности по сетям; связать загруженные фото с профилем бренда (товар.фото, оформление.логотип); бриф отдельным разделом как на проде.
Бриф отдельным разделом: то, на основании чего сервис пишет и рисует, в готовом виде. Прямо сказано, чего не хватает («описание товара, кто покупатель, фото товара») — чем меньше сервис знает, тем более общим получается результат. Своим дополнительно показывается дословный текст, уходящий в модель. В настройки добавлены палитра (по цвету в строке, с проверкой формата) и водяной знак — оставались единственными незаполнимыми через экран. Логотип и фото товара загружаются и сразу попадают в профиль.
feature
Ролс: экран для существующей линии
Линия ролса в ядре готова и проверена, экрана нет. На проде 303 строки.
Отдельный раздел: тут не «пост с картинкой», а одна мысль, которую надо произнести. Линия в ядре была готова давно — не хватало места, откуда её позвать. Заказ идёт общим путём: тот же кошелёк, очередь и номер для поддержки. Черновик, цена на кнопке, лента прошлых ролсов.
feature
Единица тарификации в реестре провайдеров
ДЕФЕКТ. У модели одна цена — за вызов. У Kie половина видеомоделей тарифицируется за секунду (wan 2.2 Animate $0.03–0.0625/с, kling 3.0 motion control $0.10–0.135/с), у части ещё и разрешение меняет цену. Признак «заСекунду» сейчас у одной модели — это заплатка. Нужна единица тарификации у каждой записи. На проде это уже стоило ~150 ₽ убытка с генерации. До починки студию не расширяем. 14.08: сверено с каталогом Kie (400 моделей): per video 111, per second 93, per image 78. Признак «заСекунду» проставлен четырём моделям, цены переведены в секунды с сохранением итога за обычный ролик (аватар 22 ₽/с, wan 2.7 12 ₽/с, seedance 13,5 ₽/с). Осталось главное: заменить булев признак полем единицы (per_video / per_second / per_image / per_request / per_1000_chars) и учесть разрешение. Имена моделей заодно исправлены — из восьми верным был один.
Сделано вместе с себестоимостью: тип Расход с полем «единица». Половина видеомоделей Kie тарифицируется посекундно, и признак «заСекунду» был заплаткой на месте, где нужна единица измерения. Проверка следит, что посекундные модели дорожают с длительностью.
feature
Тренды: YouTube
Перенесено с рабочего сервиса: YouTube Data API, два запроса (поиск + статистика), короткие ролики за месяц по числу просмотров. Отбор тот же — до 90 секунд и от 1000 просмотров. Ничего не храним: тренд живёт неделю, вчерашний список хуже, чем никакой. По умолчанию ищем по товару бренда. Нужен YOUTUBE_API_KEY в окружении.
Перенесено с рабочего сервиса: YouTube Data API, два запроса (поиск + статистика), короткие ролики за месяц по числу просмотров. Отбор тот же — до 90 секунд и от 1000 просмотров. Ничего не храним: тренд живёт неделю, вчерашний список хуже, чем никакой. По умолчанию ищем по товару бренда. Нужен YOUTUBE_API_KEY в окружении.
feature
Аналитика: Upload-Post
Перенесено: метрики тянутся из Upload-Post (Authorization: Apikey), снимок за день ложится в core.analytics_daily. Крон раз в сутки в 03:10. Снимок, а не пересчёт по требованию: площадки отдают только «сейчас», истории у них не спросишь. Пропустили день — дырка навсегда, и это сказано человеку прямо на экране. На экране первым идёт изменение за период, а не текущее число: «12 400 подписчиков» не говорит ничего, «плюс 340 за месяц» говорит всё. Подписчики считаются разницей первого и последнего снимка, а не суммой по дням: это состояние, а не событие. Нужен UPLOAD_POST_API_KEY.
Перенесено: метрики тянутся из Upload-Post (Authorization: Apikey), снимок за день ложится в core.analytics_daily. Крон раз в сутки в 03:10. Снимок, а не пересчёт по требованию: площадки отдают только «сейчас», истории у них не спросишь. Пропустили день — дырка навсегда, и это сказано человеку прямо на экране. На экране первым идёт изменение за период, а не текущее число: «12 400 подписчиков» не говорит ничего, «плюс 340 за месяц» говорит всё. Подписчики считаются разницей первого и последнего снимка, а не суммой по дням: это состояние, а не событие. Нужен UPLOAD_POST_API_KEY.
feature
готовS10 · С9 · Эксплуатация8/8

Цель: Метрики, тревоги, копии, релизы, признаки функций, инструкции по симптомам

Метрики и экран «Здоровье»
очередь, провалы, длительности, провайдеры, деньги
Очередь на экране: ждут, в работе, сдалось, возраст старейшей. Экран сразу поймал задачи-сироты после удаления данных
feature
Тревоги в Telegram
молчим, когда всё хорошо
core.alerts + tools/deliver-alerts.ts кроном каждые 5 минут. Наружу через тот же туннель, что и OpenRouter: напрямую cfru не пускает.
feature
Сквозной идентификатор запроса
цепочка поднимается за минуты, Sentry
След вида цз-xxxxxxxx через заказ, шаги, задачи провайдеров и записи денег.
feature
Резервные копии и проверка восстановления
ежемесячно на отдельной машине
tools/backup.sh ночью + tools/restore-check.ts еженедельно. Восстановление реально проверено: дамп снимается через docker exec supabase-db (pg_dump 17 против сервера 15.8 давал нерабочий дамп), расширения создаются до восстановления.
feature
Признаки функций и общий рубильник
включение и выключение без выкладки
src/features.ts: 11 признаков, остановка приёма заказов с обязательной причиной, видно на экране состояния.
feature
Проверка секретов на старте
тестовый ключ платёжки в боевом не даёт подняться
src/env.ts требоватьНастройки(): неполное окружение обрывает старт. Рабочий сервис три недели отвечал 200 без ключа очереди — здесь так не выйдет.
feature
Инструкции по симптомам и журнал инцидентов
после каждого инцидента новый тест
docs/ПО-СИМПТОМАМ.md и docs/ИНЦИДЕНТЫ.md ведутся по ходу: каждый разбор заканчивается записью, иначе разбирается заново.
feature
Разметка действий клиента и раздел «События»
Фиксировать, что человек делает в кабинете: в какой раздел зашёл, что нажал, что отправил, чем закончилось. Отдельно — ошибки: и серверные, и те, что случились в браузере при нажатии. Зачем: сейчас на «нажал, ничего не произошло» ответить нечем. Сквозной след закрывает путь заказа, но не путь человека до заказа. Состав: таблица событий с RLS и сроком хранения; запись переходов по разделам на сервере; запись нажатий и ошибок браузера через серверное действие; раздел «События» с фильтром по человеку, разделу и исходу; связь с номером для поддержки. Содержимое полей не пишем — только адреса, названия элементов и исход.
core.client_events с RLS по организации и сроком хранения 90 дней. Пишутся переходы, нажатия, отправки и ошибки браузера (включая необработанные обещания), плюс ширина экрана. Содержимое полей не пишется никогда. Смысловые события: вход, отправка в работу с темой, «заполнено 2 раздела из 4» в настройках, отказы с причиной. По замечанию владельца: раздел живёт в АДМИНКЕ (/admin/events), из кабинета клиента убран, вход по признаку core.people.staff — посторонний получает «нет такой страницы», а не «нет прав». Читается словами, а не адресами: «Перешёл в СММ-Центр, Календарь», «Нажал «Отправить в работу» в разделе Создать контент». tools/client-events.check.ts — 20 проверок, включая запрет английских ключей и адресов на экране.
feature
готовS11 · С10 · Перенос данных10/10

Цель: Клиенты и контент переезжают без потерь, с отчётом по каждой таблице

Перенос организаций, брендов, людей, прав
вместе с ролями
tools/perenos-lyudi.ts: 339 организаций, 490 людей, 339 брендов, 340 участников. Повторяемый — второй прогон ничего не задваивает. Ключи рабочего сервиса сохраняются, поэтому все будущие ссылки (работы, деньги, публикации) сойдутся. Права развёрнуты из ролей: в рабочем сервисе они пустые у всех 340, а ядро пустые запрещает. Живой случай при переносе: владелец существовал в обеих системах с разными ключами — заведена core.перенос_соответствие (миграция 0045), по ней разбираются все кучи. Гейт зелёный на новых объёмах, поиск 22 мс.
feature
Перенос банков качества и аналитики
референсы, вирусные шаблоны, словарь, шаблоны ролсов
tools/perenos-banki.ts: 1161 хук в 18 нишах, 423 образца оформления с картинками, 23 ударения, 6 примеров сценариев, 167 ежедневных снимков метрик. Повторяемый. Снимки метрик — самое ценное: задним числом их не собрать, площадки прошлое не отдают. Снимок в рабочем сервисе привязан к заводу, у нас к бренду; несколько заводов одного бренда за день сводятся по наибольшему, а не суммой — подписчики это состояние, а не событие. Ударения и примеры с уже занятым словом или коротким именем пропускаются: уникальность там по смыслу, а не по ключу.
feature
Инвентарь 115 таблиц
переносится, не переносится, почему
docs/ПЕРЕНОС-ДАННЫХ.md: все 115 таблиц рабочей базы разобраны поимённо. Переносим 67 (22 788 строк), потом 18 (Студия, Лия, Вирус, Входящие), не переносим 30 (журналы, очереди, кэши; один scheduler_logs — 109 тысяч строк). У каждой строки написано куда в ядре и зачем.
feature
Перенос готовых работ и файлов
в артефакты, с проверкой открываемости
tools/perenos-raboty.ts: 725 работ, 1860 файлов, все в состоянии «готово», все ссылки проверены живьём (200). Состояние не проставляется руками — работа проходит теми же переходами, что и настоящая, и оставляет те же события. Разбор файлов оказался главной работой. Завершённых работ в рабочем сервисе 2540, из них с настоящим файлом 1538, а перенеслось 725. Разница — файлы, которых уже нет: · 809 роликов и 63 аватара лежат на временных адресах провайдеров (tempfile.aiquickdraw.com, files2.heygen.ai). Проверено: 403. Они не открываются УЖЕ СЕЙЧАС, в самом рабочем сервисе. Это его потеря, не переноса: провайдерские ссылки сохраняли вместо файлов; · ещё около тысячи завершённых работ хранят картинку строкой base64 прямо в базе, файла как такового нет. Живые файлы лежат в двух местах: 1575 в Яндексе прежнего сервиса и 285 в нашем Supabase. Копировать восемь гигабайт не понадобилось: введена вторая, явно помеченная форма пути «наследие/источник/путь» (миграция 0047), ссылка умеет её отдавать. Приставка read-only по построению: пути для новых файлов строит одно место и всегда начинает с core/.
feature
Перенос балансов и истории трат
копейка в копейку
tools/perenos-dengi.ts: 4660 операций, остаток совпал у всех до копейки. Итог 113 330,40 ₽ и там и там; в минусе ровно те четверо, кто в минусе и в рабочем сервисе. Две находки. Первая: журнал операций в рабочем сервисе НЕПОЛНЫЙ — у 326 человек из 491 хранимый баланс не сходится с суммой операций (у одной 11 ₽ при сумме операций минус 3487 ₽). Клиент видит хранимое число, значит правда в нём. После переноса движений добавляется одна подписанная запись выравнивания на кошелёк. Без неё 116 кошельков ушли бы в минус, и человек увидел бы долг. Вторая: историческая трата не привязана к нашему заказу, а списание без заказа ядро не принимает. Переносим корректировкой с честной причиной — на баланс не влияет, история видна. Деньги личного кабинета лежат на кошельке организации, а не бренда: в рабочем сервисе счёт у человека один на всё, и дробить его по брендам значило бы отобрать часть. Ярлык «Оплачивает агентство» заменён на «Платит организация» — для личного кабинета никакого агентства нет.
feature
Сверка после переноса
числа сходятся, отчёт по остаткам
tools/sverka-perenosa.ts: 19 проверок разом по всем кучам, зелёная. Встроена в гейт: перенос уже сделан, и сломать его незаметно правкой схемы или новой миграцией теперь нельзя. Проверяет: людей и слияния по почте, права у каждого участника, живого владельца у каждой организации, заводы и товар в профиле, кошелёк у каждого самостоятельного бренда, общий остаток и остаток ПОИМЁННО, отсутствие новых минусов, готовность всех работ, наличие версии, все файлы в нашем хранилище, отсутствие публикаций несогласованного, подключение у каждой публикации. Заодно печатает, что осознанно осталось в рабочем сервисе: 878 работ с умершими ссылками провайдеров, 116 с картинкой строкой в базе, 1643 незавершённых.
feature
Перенос заводов и профилей брендов
Заводы рабочего сервиса и профили брендов в ядро.
tools/perenos-zavody.ts: 169 заводов, у всех есть бренд и форматы, у всех брендов заполнен товар в профиле. Брендов в ядре стало 497. Главное открытие: в рабочем сервисе завод принадлежит ЧЕЛОВЕКУ, а компании появились позже и достались меньшинству. Из 169 заводов только 4 привязаны к компании, 158 висят на людях вне организаций. При этом именно они и делают работу: 457 из 482 работ за последние 90 дней, 20 их владельцев платили деньги. В ядре всё держится на бренде, поэтому для таких людей заводится личный кабинет (организация + бренд по связке «человек и товар»). Без этого 95% живых пользователей после переключения открыли бы пустой кабинет. Ключи считаются от исходных, поэтому повторный запуск ничего не задваивает. Пауза переносится с причиной — инвариант ядра запрещает паузу без причины.
task
Перетащить файлы из Яндекса в своё хранилище
1575 файлов перенесённых работ лежат в Яндексе прежнего сервиса (content-factory-2.storage.yandexcloud.net). Сейчас они открываются, и работы показываются правильно. Но это чужой ядру счёт: выключат его — и артефакты опустеют, а ровно от этого и защищает INV-083. До переключения перетащить их в наш Supabase по обычным путям core/орг/бренд/работа/версия/имя и переписать пути в артефактах. Артефакты неизменяемы, поэтому новая версия артефакта, а не правка старой. Объём: около 2-3 ГБ по пробе (средний файл 1,6 МБ).
tools/pereezd-faylov.ts: 1575 файлов перевезены из Яндекса прежнего сервиса в наше хранилище. На чужом счету не осталось ничего — проверка tools/svoi-fayly.check.ts в гейте, 1863 файла все у нас. Байты не трогали: скачали, положили по своему пути, переписали адрес. Никакого приведения к канону — файл уже принят клиентом, второй прогон через ffmpeg только испортил бы качество. Артефакт неизменяем, и новой версией это делать было нельзя: на первую ссылаются согласование и публикации, а версия 2 означала бы «пересобрано», чего не было. Поэтому узкая помеченная щель (миграция 0051): при переезде меняется ТОЛЬКО список файлов и только когда соединение прямо об этом заявило. Стоимость, состав, признак полноты остаются неприкосновенными. Два урока по дороге. Все слайды карусели я записал одной ролью «слайд», а имя файла строится из роли — второй слайд ложился поверх первого, и хранилище это отклонило (INV-012). Теперь роли нумеруются, как в линии карусели. И переезд стал по-настоящему повторяемым: путь считается до скачивания, уже лежащий файл не качается заново — прошлый прогон оборвался ровно между отправкой файла и записью в артефакт. Заодно: имяФайла() теперь понимает роли с номером — «слайд-2» становится slide-2, а не «-2».
task
Перенос публикаций и подключений к сетям
publication_logs в core.publications, подключения в core.social_accounts.
tools/perenos-publikacii.ts: 914 публикаций, 35 подключений к сетям (youtube, instagram, telegram, vk, threads, tiktok). Из 2593 записей журнала легли те 920, что относятся к перенесённым работам; 6 повторов схлопнуто — одна работа в один аккаунт публикуется один раз. Подключения заведены в состоянии «отвалился»: токены в рабочем сервисе лежат по заводам и компаниям вперемешку, проверить их отсюда нельзя, а публикация не в тот аккаунт хуже, чем просьба переподключиться. Историю это не портит. Три инварианта пришлось уточнить, и все три — по делу, а не ради переноса. Отвалившийся аккаунт (0048) и пауза бренда (0049) запрещают БУДУЩУЮ отправку, но не переписывают прошлое: публикация, которая вышла полгода назад, от сегодняшнего токена не зависит. А согласование ослаблять не стал — вместо этого переносится сам факт: работа, которая вышла, одобрение проходила.
task
Перенос тарифов и действующих подписок
subscription_plans и активные подписки клиентов в ядро.
НАЙДЕНО ПРОВЕРКОЙ, а не по плану: у пятерых действующие платные подписки от 4 990 до 29 990 ₽ в месяц, а в ядре не было ни подписок, ни тарифов. При переключении эти люди потеряли бы оплаченное и узнали бы об этом сами — попробовав сделать то, за что платят. tools/perenos-podpisok.ts: 6 тарифов и 5 действующих подписок. Истёкшие не переносим — в ядре они создавали бы вид, что человек платит. Ограничения тарифа сложены в один объект: в рабочем сервисе они разбросаны по полутора десяткам колонок, и каждая новая возможность добавляла ещё одну. У нас это данные, а не схема. Подписка в рабочем сервисе у человека, у нас — у организации: платит она, и её ограничения касаются всех её брендов. Из пятерых двое — тестовые учётки рабочего сервиса, трое настоящие: start до 07.09, pro до 11.09, agency до 07.07.2027. Сверка переноса теперь следит: действующая подписка обязана быть и у нас, и у каждой должен быть тариф.
task
В РАБОТЕS12 · С11 · Приёмка и переключение46/54

Цель: Нагрузка, хаос по каталогу отказов, аудит доступа, сверка денег, день переключения

Прогон каталога отказов
ЗАМЕРЕНО (tools/katalog-otkazov.check.ts, в гейте): описано 248 отказов и 83 инварианта; с кодом связаны 3 отказа и 39 инвариантов. Число «3 из 248» выглядит приговором, но мерит оно прослеживаемость, а не защиту. Проверок пятьдесят восемь наборов, поведение они держат — просто ни одна не называет номер отказа, который закрывает. Проверено выборочно: INV-004 (публикация не меняет состояние работы), INV-013 (артефакт не создаётся, пока не уложены файлы), INV-021 (заказы создаёт только человек или план), INV-025 (шаг не стартует раньше зависимостей) — все четыре реально защищены кодом, номер не назван ни разу. Задачу НЕ закрываю: прогнать каталог по-настоящему значит для каждого отказа показать проверку, которая его воспроизводит. Пока сделан замер и храповик — падать покрытию больше нельзя.
feature
Сверка денег с провайдерами и платёжками
суммарные траты сходятся
tools/perenos-platezhey.ts: перенесены 105 платежей ЮKassa (60 оплаченных на 206 526 ₽, 45 незавершённых). Незавершённые тоже: брошенная корзина — это ответ на вопрос «почему нет денег, хотя человек говорит, что платил». СВЕРКА: заплачено через кассу 206 526 ₽, начислено пополнениями 314 479 ₽. Разница 107 953 ₽, из них 4 000 ₽ бонусами. Остальные 103 953 ₽ — ручные пополнения прежнего сервиса без записи о платеже. Систематики нет: у 15 человек совпадает точно, у 1 ровно вдвое, у 9 по-разному. Главное: НИ У КОГО не начислено меньше уплаченного. Обратное было бы бедой — человек заплатил, а денег нет. Владельцу на заметку: 103 953 ₽ начислено вручную. Если это не осознанные пополнения, стоит посмотреть, откуда они брались.
feature
Нагрузка
500 заказов, 5000 работ в списках, 50 пользователей
tools/nagruzka.check.ts: полсотни человек открывают экраны одновременно, каждый запрос идёт через политики доступа. Список работ 127 мс по девяносто пятому проценту, поиск 36, расходы 105, публикации 39, смешанная нагрузка 483 мс — все пятьдесят обслужены за 536 мс. Разобрался с узким местом: это НЕ размер пула. Подняв его с десяти до шестнадцати, получил партии медленнее (122 → 158 мс) при том же общем времени — запросы делят одни ядра. Пул всё равно сделал настройкой CZ_DB_POOL: ручка пригодится, а константа в коде — нет. Первый потолок в 400 мс на смесь был взят наугад, и проверка справедливо не уложилась. Поставлен честный: полсотни человек В ОДНУ СЕКУНДУ — примерно десятикратный запас к настоящей одновременности при пятистах пользователях, и обслужить их меньше чем за секунду нормально. Порог держим, чтобы поймать поломку (три секунды вместо полусекунды), а не чтобы гнаться за числом. Ещё: первая редакция проверки мерила выборку по всей базе без единого условия — такого экрана нет. Исправлено на запросы, которые делают настоящие страницы.
feature
Аудит доступа
ноль успешных попыток достать чужое
tools/audit-dostupa.check.ts на НАСТОЯЩИХ данных: 25 живых людей вперемешку, смотрим их глазами. Каждый видит свои бренды, ни один не видит чужой, ни один не видит всю базу (571 бренд, 733 работы, 5012 записей денег), ни один не видит чужие деньги. Чужой бренд, работа, артефакт и публикация не открываются и по прямой ссылке. Служебная таблица соответствий переноса клиенту не видна. Отличие от access.check.ts: тот проверяет матрицу ролей на выдуманных организациях, где всё аккуратно. Этот — на данных 491 живого человека после переноса. Обе в гейте.
feature
Прогон сценариев
глазами СММщика и предпринимателя
tools/scenarii.check.ts в гейте: путь клиента целиком одним прогоном — регистрация, подтверждение почты, вход, кабинет с брендом, бриф, завод, деньги на счету, границы (чужой бренд не открывается). 12 проверок, за собой убирает. Смысл отдельный от прочих проверок: те смотрят части, эта — связку. На стыках всё и ломается: права есть, а кнопки нет; деньги списались, а работа не появилась. Денег не тратит: заказ не создаётся, касса не зовётся. Производство закрыто своими проверками и живым прогоном. НАШЛА ОШИБКУ СРАЗУ: ручная правка счёта в админке писалась видом «начисление», а инвариант требует для него заказ (начисление — это про партнёрские выплаты). Поддержка не смогла бы вернуть деньги вообще. Исправлено на корректировку.
feature
День переключения
домен, расписания, план отката, откат по кнопке
docs/ДЕНЬ-ПЕРЕКЛЮЧЕНИЯ.md: пошаговая книга на 14 отмечаемых шагов, писалась под человека, который перенос не делал — каждый шаг с командой и признаком «получилось». Порядок: заморозить рабочий сервис (не выключить — перестать принимать новое), догнать перенос десятью прогонами, проверить гейтом, развести адреса, закрыть старый кабинет, первый час смотреть админку. Главное правило дня записано первым: откат остаётся возможным до самого конца. Данные в public не меняются — ядру запрещена туда запись ролью базы. Рабочий сервис не удалять неделю: половина проблем вылезает на третий день. Отдельным разделом — что клиент заметит и это нормально: 19 сетей надо переподключить, 878 работ не откроются (их адреса мертвы и сейчас), ещё около тысячи хранят картинку строкой в базе. Задача остаётся открытой: книга написана, но день не наступил.
feature
Глазами: как выглядит приведение к канону
Владелец опасается, что приведение формата даст кривой результат. Собрать ролики из НАСТОЯЩИХ клипов Kie разных форматов (704x1248, 720x1280, квадрат, горизонталь) и посмотреть глазами: нет ли широких чёрных полей по краям, не мылится ли картинка после масштабирования, не рвётся ли движение при смене 24-25 кадров на 30, не разъезжается ли звук по времени. Проверить на телефоне, а не только на большом экране. Если поля мешают — обсудить альтернативы: размытый фон вместо чёрного, умная обрезка по центру внимания, или требовать у провайдера нужное разрешение сразу. Команда: tools/media.check.ts делает то же самое на синтетике, здесь нужны реальные клипы и человеческий глаз.
check
Глазами: полный путь клиента на стенде
Пройти сценарий целиком как СММщик: создать бренд, собрать план, запустить работу, посмотреть ход сборки, согласовать, опубликовать. Отдельно как предприниматель: понятно ли, сколько стоит, за что списали, где результат. Проверить на телефоне.
Прошёл браузером по кабинету с настоящими данными: дашборд, работы, публикации. Оформление перенесено с рабочего сервиса как есть, разделы на месте, картинки живые, пустые состояния честные («Публикаций пока нет. Подключите сеть выше»). НАШЁЛ ДВЕ ВЕЩИ. 1. Выравнивание остатков ОБНУЛИЛО показательный бренд стенда: минус 2929 ₽. Правило «у компаний в рабочем сервисе счёт нулевой» я применил ко всем кошелькам подряд, а этот бренд к переносу отношения не имеет. Перенос не имеет права трогать то, что не переносит. Деньги возвращены встречной записью (tools/vernut-svoe.ts), выравнивание ограничено перенесёнными. 2. Карточка работы без картинки растягивалась до высоты соседней с картинкой — пустая коробка в пол-экрана. Сетке задана своя высота ячеек.
check
Отдельная роль в базе для ядра (сейчас ходит суперпользователем)
Выяснилось при переносе: схема core и рабочая база public живут в ОДНОМ Postgres (контейнер supabase-db, 172.19.0.9). Для переноса это удобно — он идёт запросом внутри базы, без выгрузки. Но ядро ходит туда суперпользователем postgres и с search_path "core, public": запрос без указания схемы к таблице, которой нет в core, попадёт в рабочие данные клиентов. Одна опечатка — и правим прод. Что сделать: роль cz_core с полными правами на core и только чтением на public; миграции по-прежнему под postgres отдельной строкой подключения; проверка в гейте, что запись в public роли ядра недоступна. До переключения — обязательно.
Роль cz_core (миграция 0050): полный доступ к core, ТОЛЬКО ЧТЕНИЕ public, без суперправ. Обход политик оставлен намеренно — служебные пути (воркер, сборка, сверки) видят всю картину, а запросы от имени человека по-прежнему идут под cz_app через SET LOCAL ROLE, и у неё обхода нет. Миграции ушли на отдельную строку подключения DATABASE_URL_ADMIN: менять схему приложению не нужно и незачем. Проверка tools/dostup-bazy.check.ts в гейте, 7 утверждений: роль служебная, суперправ нет, обход есть, рабочую базу читать можно, писать нельзя (база отклоняет), запрос от имени человека идёт под cz_app без обхода. По дороге: bash не принимает кириллицу в именах переменных — присваивание молча выполняется как команда и падает в другом месте. Проверка ascii-wire.check.ts теперь смотрит и на скрипты оболочки, поймано на нарочно сломанном файле.
task
Решить, что делать с потерянными файлами работ
Владельцу на решение. У 872 завершённых работ файл лежал на временном адресе провайдера и уже недоступен (403 у Kie и HeyGen). Ещё около тысячи хранят картинку строкой base64 в самой базе. Варианты: (а) не переносить вовсе — сейчас так и сделано, клиент их и так не откроет; (б) перенести карточки без файла, чтобы в истории остался след, что работа была; (в) для base64 — вытащить строки в хранилище (121 картинка в content_items плюс слайды каруселей) и перенести как обычные файлы. Проверка: SELECT count(*) FROM public.content_items WHERE status IN ('published','approved','ready') AND media_url LIKE 'data:%';
decision
Переподключить сети брендам перед переключением
35 подключений перенесены в состоянии «отвалился»: истории они не мешают, но публиковать через них нельзя, пока не подтвердим доступ. Часть можно закрыть без клиента: сети, которые идут через Upload-Post, опознаются по профилю, а ключ Upload-Post у нас есть — профили можно сверить запросом и перевести в «подключён». Остальным (прямой Телеграм, ВК) нужен токен от владельца бренда. Проверка: SELECT network, count(*) FROM core.social_accounts WHERE state = 'отвалился' GROUP BY 1;
Сверено нашим ключом Upload-Post (tools/sverit-seti.ts, только чтение): из 35 подключений 16 подтверждены площадкой и переведены в рабочее состояние. Остальные 19 остаются отвалившимися: у 10 профиля в Upload-Post нет вовсе, у 9 профиль есть, но сеть к нему не привязана. Им нужен владелец бренда — токен прямого Телеграма или ВК отсюда не проверить.
task
Подключить настоящую отправку в соцсети
Перенесённый клиент Upload-Post не был подключён к отправке.
НАЙДЕНО: перенесённый побайтово клиент Upload-Post (1590 строк) лежал мёртвым кодом — транспорт по умолчанию просто отказывал «отправка не настроена». То есть публикация в ядре не работала вообще, при том что раздел выглядел готовым. Сделан переводник src/lines/publish/транспорт.ts: наши понятия в понятия клиента, сам клиент не тронут (он перенесён побайтово и сверяется проверкой). Разбирает ответ по своей площадке: адреса и номера постов клиент отдаёт россыпью по сетям. СРАЗУ ЖЕ закрыт рубильник «публикация» с причиной: транспорт настоящий, и до прямого разрешения владельца ни один пост в чужую ленту уйти не должен. Проверка отправки поднимает признак на время прогона и возвращает как было — она работает через заглушку и наружу не ходит.
task
Назвать номера отказов и инвариантов в проверках
Механический проход: у каждой существующей проверки дописать, какой отказ (FAIL-NNN) и какой инвариант (INV-NNN) она закрывает. Защита уже есть — не хватает возможности показать на неё пальцем. Цена вопроса видна сразу: сейчас на вопрос «это правило точно исполняется?» нельзя ответить иначе, чем перечитывая код. Через полгода или при передаче другому человеку это и есть разница между документом и договором. Порядок: сначала 44 инварианта, которые пока только на бумаге (список печатает tools/katalog-otkazov.check.ts), потом отказы. После каждой порции поднимать храповик CZ_FAIL_BASELINE и CZ_INV_BASELINE.
task
Вход и регистрация: сейчас только по приглашению
В рабочем сервисе 6 страниц входа: регистрация, восстановление пароля, смена пароля, подтверждение почты, вход через соцсети, возврат от OAuth. В ядре — только вход по приглашению и выход. Без этого новый человек не сможет зарегистрироваться, а забывший пароль — войти. Переключаться нельзя. В core.people уже есть auth_user_id под Supabase Auth — задел под это и делался.
src/auth.ts + новая страница входа: вход по паролю, регистрация, напоминание пароля. Приглашения переехали на /login/invite и работают. ГЛАВНОЕ РЕШЕНИЕ: пароли не наши. Они лежат в Supabase Auth того же контура — 485 учётных записей, все сходятся с людьми ядра по почте. Своя проверка паролей означала бы «придумайте новый» для всех разом. Проверяет их Supabase, сеанс открываем свой: наша кука, наши сроки, наш список устройств. Регистрация сразу заводит кабинет: организацию, бренд и кошелёк. Ключи те же, что у переноса, — зарегистрировавшийся сегодня и перенесённый завтра попадут в один кабинет, а не в два. НАЙДЕНО ПО ДОРОГЕ: у 228 владельцев личных кабинетов было ПО ОДНОМУ праву вместо тринадцати. Запрос «ARRAY(SELECT unnest(permissions) … LIMIT 1)» читается как «взять права владельца», а означает «взять одно право». Эти люди вошли бы и не смогли ничего. Права теперь берутся из кода (src/states.ts), данные починены — 568 владельцев по 13 прав. Проверка tools/auth.check.ts в гейте, 14 утверждений: регистрация заводит кабинет с брендом и кошельком, без подтверждения почты не пускает, вход по паролю работает, с неверным не пускает, на несуществующую почту ответ такой же (иначе форма становится способом узнать список клиентов).
task
Приём оплат: ЮKassa и счета Точки
В ядре есть таблица платежей и перенесена история (105 платежей), но нет самого приёма денег: ни оформления платежа в ЮKassa, ни выставления счёта Точки, ни возврата с их стороны. Пока этого нет, ядро не самодостаточно: пополнить счёт можно только через старый кабинет, а он пишет деньги в свою схему — и в ядре их не будет. В проде: src/app/api/billing/tochka/*, обработчики ЮKassa, страница pricing с тарифами.
ЮKassa работает: клиент перенесён побайтово, зачисление проверено, кнопка пополнения и обработчик уведомлений на месте. ТОЧКА — отдельная история. Клиент перенесён (95 строк, сверяется проверкой), но НЕ ВКЛЮЧЕН, и вот почему: в рабочем сервисе она тоже не настроена. У работающего процесса прода НОЛЬ переменных TOCHKA — то есть выставить счёт нельзя и там. Два счёта в базе от 30 июля оказались тестовыми: один человек, один ИНН, «ООО Иванов» и «ООО АСТРА ТЕЛЕКОМ», ни один не оплачен. Настоящий клиент счёта не ждёт. Строить интерфейс поверх ненастроенной интеграции и «проверять» его без ключей — способ обнаружить неработающую оплату в день, когда за ней придёт юрлицо. Ждёт ключей и решения владельца.
task
Рабочий минимум админки
В рабочем сервисе 27 админских страниц, в ядре одна (события). Для эксплуатации после переключения нужны рычаги: люди, операции, остатки, очередь, ошибки, поддержка, тарифы, промокоды. Без них после переключения нельзя вернуть деньги, разобрать зависшую очередь и ответить в поддержку — а это первое, что понадобится. Остальное (обучение ИИ, статистика студии, рассылки) переезжает вместе со своими разделами.
Готово: обзор, застрявшее, люди с карточкой и ручной правкой счёта, тарифы с подписчиками, промокоды с учётом применений, события, здоровье. ПОДДЕРЖКА оказалась не разделом кабинета. Проверил: все восемь обращений в рабочем сервисе пришли из Телеграма (6) и MAX (2), ни одного из кабинета, и отвечают туда же. Это интеграция с ботом, и переносить её в кабинет нечего — без бота отвечать будет некуда. Таблицы обращений в ядре заведены (миграция 0055) и ждут, когда бот переедет; данные пока остаются в рабочем сервисе. Канал поддержки при переключении не ломается: он и сейчас живёт отдельно от кабинета. ПРОМОКОДЫ: 4 кода и 7 применений перенесены. Учёт применений держит база (один человек — один раз на код), а не код экрана: иначе бонус превращается в источник денег.
task
Мастера создания заводов
В проде 10 страниц раздела factory, включая мастера создания под разные виды: музыкальный, продавец, история, Threads. В ядре — список заводов и карточка завода. Сами заводы перенесены и работают, не хватает удобного заведения новых.
task
Развести адреса на nginx: кабинет в ядро, витрина на месте
ГЛАВНОЕ ОГРАНИЧЕНИЕ: оба сервиса делят один Postgres. Рабочий пишет деньги в public.transactions и profiles.balance, ядро — в core.wallet_entries. Запустить оба сразу нельзя: деньги клиента раздвоятся. Предложение: витрину (лендинг, тарифы, оферта, политика, условия, контакты, блог, документацию, обновления — 11+ страниц) не переносить вовсе, она клиентских данных не трогает. Кабинет и производство отдать ядру, старый кабинет закрыть. Порядок и полная карта: docs/ЧТО-ОСТАЛОСЬ.md
decision
Экран пополнения и обработчик уведомлений кассы
Механика оплаты готова и проверена (src/payments.ts). Не хватает двух вещей, чтобы человек мог заплатить: 1. Кнопка «Пополнить» на экране расходов: сумма, переход в кассу, возврат со страницей «ждём подтверждения». 2. Обработчик POST /api/pay/yookassa — принимает уведомление, зовёт принятьУведомление, отвечает 200. Адрес обработчика прописать в личном кабинете ЮKassa. Ключи YOOKASSA_SHOP_ID и YOOKASSA_SECRET_KEY взять с рабочего сервиса (там же лежат). ВАЖНО: на боевом контуре тестовый ключ = отказ работать, это защита из перенесённого клиента.
Кнопка «Пополнить» на экране расходов: четыре готовые суммы и поле для своей, возврат со страницей «платёж отправлен, ждём подтверждения». Видна только при праве «пополнять». Обработчик POST /api/pay/yookassa. Два правила: отвечаем 200 почти всегда (касса повторяет уведомление, пока не получит двухсотый, и разбираться в нём всё равно нам), отказ только с чужого адреса. Ключи кассы перенесены с рабочего сервиса. Живой платёж НЕ создавался: ключ боевой, и проба означала бы настоящее списание. Проверена вся механика на записях в базе. Заведена настройка CZ_SITE_URL — публичный адрес, куда касса возвращает человека. Подставлять его в последний момент значит однажды отправить клиента в никуда. ОСТАЛОСЬ: прописать адрес обработчика в личном кабинете ЮKassa (это делается руками владельцем) и счета Точки для юрлиц.
task
Включить счета Точки: нужны ключи и решение
Клиент банка перенесён и лежит готовый (src/lines/pay/tochka.ts). Не включён, потому что интеграция не настроена и в рабочем сервисе: у его процесса ноль переменных TOCHKA_API_TOKEN / CUSTOMER_CODE / ACCOUNT_ID. Вопрос владельцу: нужны ли счета для юрлиц вообще? Если да — дать ключи, и тогда делается экран «выставить счёт» с ИНН и названием плательщика плюс опрос состояния оплаты. Если нет — юрлица платят картой как все, и эту часть можно закрыть совсем. Два счёта в базе от 30.07 тестовые, настоящих клиентов на счетах нет.
СДЕЛАНО. Задача была сформулирована неверно с моей стороны: ключи Точки у владельца давно есть, он говорил об этом дважды. Ключи лежали на стейдже content-factory, перенесены в ядро. Клиент банка был перенесён побайтово ещё раньше, но его НИКТО НЕ ЗВАЛ: кнопка «Счёт для юрлица» заводила запись в нашей базе и обещала прислать документ, а присылать было нечего. Теперь при выставлении счёт создаётся в Точке и уходит на почту бухгалтерии письмом от банка вместе с PDF, номер документа хранится в payments.external_id, отмена удаляет счёт и в банке. Две находки по дороге: 1. TLS к банку рвался: сертификат Точки выдан Russian Trusted Sub CA (Минцифры), которого нет в наборе Node. Положил корневой и подчинённый в certs/russian-trusted.pem и прописал NODE_EXTRA_CA_CERTS. Склеивать PEM надо с переводом строки между сертификатами, иначе openssl говорит bad end line, а Node молча не доверяет. 2. ИНН теперь проверяется контрольной суммой ДО обращения в банк: на невалидном банк отвечает 424 invalid document side tax code, а человек к тому моменту уже прочитал «счёт выставлен». ПРОВЕРЕНО ЖИВЬЁМ: create → payment-status (payment_waiting) → delete, счёт удалён.
decision
Регистрация проверена живьём: кабинет, бренд и 100 ₽
ПРОВЕРЕНО. Завёл кабинет через форму как обычный человек: создались человек, организация и бренд, на счёт упало 100 ₽, письмо с подтверждением ушло (SMTP отвечает, ошибок нет). НАЙДЕНО И ПОЧИНЕНО: ссылка в письме вела на тестовый стенд. Служба входа общая на все контуры, и адрес возврата в ней задан один раз — GOTRUE_SITE_URL=test.24contentzavod.ru. Клиент боевого домена нажал бы «подтвердить» и попал не в свой кабинет. Теперь адрес возврата передаётся в запросе и берётся из заголовков: каждый контур возвращает человека к себе. То же сделано для письма со сменой пароля. К ДНЮ ПЕРЕКЛЮЧЕНИЯ: адрес кабинета должен быть в списке GOTRUE_URI_ALLOW_LIST, сейчас там только 24contentzavod.ru и test.24contentzavod.ru.
feature
Защита от потока запросов: предел частоты
СДЕЛАНО по прямой просьбе владельца («защита от большого количества кликов на создание контента»). Идемпотентность такое не ловит: она про повторную отправку одной формы, а здесь поток разных запросов — автокликер, чужой скрипт, нетерпеливый человек. Счётчики в базе (миграция 0064), у каждого действия свой предел и свой ключ, чтобы один разошедшийся клиент не закрыл сервис остальным: работы и студия — 12 в минуту на бренд, просьбы к модели — 20-40 в час, вход — 10 попыток на адрес за 10 минут, регистрация — 5 в час, письмо со сменой пароля — 3 в час. Отказ говорит по-человечески и называет срок. Проверка в воротах: 8 случаев, включая «предел одного не мешает другому» и «после окна счёт начинается заново».
feature
Витрина переехала на свои файлы и получила живое видео
СДЕЛАНО. Ролики и картинки лендинга брались с рабочего домена — в день переключения он станет нашим ядром, и первый экран осыпался бы вместе с примерами на страницах ниш. Все 31 ролик и 12 картинок скачаны, сжаты (20 МБ вместо 96) и лежат у нас. ДОБАВЛЕНО ПО ПРОСЬБЕ: в обложке теперь крутится видео, а не стоп-кадры. Взяты работы живых заводов — Брендосс, Пульс цен, мебель и наш собственный завод, 16 роликов. Играет каждая третья плитка и только когда видна: семьдесят видео разом это подвисший телефон. При системной настройке «меньше движения» не играет ничего.
feature
Админка: раздел «Деньги» по всем направлениям
СДЕЛАНО по просьбе («все расходы, транзакции считаются со всех направлений»). Раньше в админке была одна денежная цифра, и та про обязательства перед клиентами. Теперь видно раздельно: пополнили клиенты, выручка за работы, ушло провайдерам, осталось сервису с долей. Разбивка по трём направлениям — Контент-заводы, Студия, СММ-Центр, — плюс возвраты, подарки новичкам и лента последних движений по счетам. НАЙДЕНО ПРИ СБОРКЕ: себестоимость писала только студия. У заводов в отчёте выходило «провайдерам 0 ₽», то есть стопроцентная маржа. Исправлено во всех пяти линиях: пост, карусель, видео, аватар, ролс — там, где провайдер зовётся в цикле, расход копится по шагу.
feature
Контент-план: результат виден и с ним можно работать
СДЕЛАНО по замечанию («в плане нельзя посмотреть картинку, сценарий, ничего поправить»). У идеи с готовой работой теперь миниатюра в карточке и кнопка «Посмотреть»: открывается окно с результатом крупно, полным текстом поста и сценарием. Оттуда же согласовать, вернуть на доработку, скачать, открыть целиком. Окно нативное: закрывается по Esc, не требует своей библиотеки, действия — обычные формы.
feature
Оплата подписки: картой и по счёту для юрлица
СДЕЛАНО по замечанию («при нажатии на тариф не даёт выбора — юрлицо или картой, и кидает в кабинет»). Механика подписки в ядре была, а заплатить за неё было нельзя. Появилась страница «Подписка»: видно, что подключено сейчас, все тарифы с ценой и два способа оплаты. Картой — через кассу, подписка включается сама после подтверждения платежа. Для юрлица — форма реквизитов (название, ИНН, КПП, адрес, куда прислать счёт) и честный срок: банк зачисляет от нескольких часов до двух дней, автоматической сверки у нас нет, тариф включает администратор по факту прихода денег. Платёж теперь знает своё назначение (миграция 0065): пополнение или тариф. Обработчик кассы по этой пометке включает подписку — раньше он умел только зачислять на счёт, и человек, оплативший тариф, остался бы без него. ПРОВЕРЕНО ЖИВЬЁМ: касса открылась на 4990 ₽ от боевого домена, способы оплаты на месте. Платёж не совершал. Кнопки тарифов на витрине теперь ведут вошедшему на его подписку.
feature
Стена лендинга: без повторов и с перемешиванием
ПОЧИНЕНО по замечанию («похожие ролики рядом, ощущение что контента мало»). Плитки раздавались по формуле, и одинаковые работы оказывались в соседних столбцах на одной высоте. Теперь колода раздаётся по кругу: 35 видимых плиток — 35 разных работ, повторов по горизонтали ноль. Порядок перемешивается зерном от часа: человек, зашедший второй раз, видит другую подборку, а сервер и браузер собирают одну разметку (со случайным числом страница ругалась бы на несовпадение). Ролики и картинки чередуются, чтобы сетка не выходила полосатой.
feature
Витрина работ: свои файлы и раскладка без прокрутки
ПОЧИНЕНО. Блок «Видео / Карусели / Посты» тянул файлы с рабочего домена (в день переключения он станет нашим ядром) и был жёстко в шесть колонок — на узком экране появлялась системная полоса прокрутки поперёк блока, та самая «кривая». Колонки стали резиновыми: сколько влезло, столько в ряду, остальное переносится. Файлы свои.
feature
Админка: счета, ждущие оплаты
СДЕЛАНО по просьбе. Раздел «Счета» показывает, кто ждёт: название организации, ИНН, КПП, почта, кабинет, тариф, сумма и сколько дней счёт висит. Две кнопки: «Оплата пришла — включить тариф» и «Закрыть без оплаты» (для передумавших и дублей). Включение делает всё разом: зачисляет сумму на счёт клиента, помечает платёж оплаченным и включает подписку на месяц. Деньги идут через кошелёк, а не мимо: у сервиса двойная оплата, и иначе юрлицо платило бы дважды. ПРОВЕРЕНО СКВОЗНЯКОМ: выставил счёт из кабинета, увидел его в админке с реквизитами, включил тариф — подписка активна на месяц, платёж оплачен, деньги на счёте. Следы проверки убраны корректирующей проводкой: правка проводок в этом ядре запрещена самой базой, и это правильно.
feature
Текст про оплату счёта: что делать клиенту
ПОПРАВЛЕНО по решению владельца. Раньше человек читал «включим, когда деньги придут на расчётный счёт» и оставался ждать неизвестно сколько. Теперь сказано прямо: оплатили — напишите в поддержку с платёжкой, тариф включим сразу, не дожидаясь прохождения денег.
feature
Витрина работ: прокрутка убрана совсем
ПОЧИНЕНО. Прошлая правка не подействовала: ниже в файле оформления лежало моё же старое правило, которое делало из витрины ленту с прокруткой вбок (grid-auto-flow: column). Отсюда и плитки разной ширины: одно правило задавало резиновые колонки, другое фиксировало 210 пикселей. Осталось одно правило: пятнадцать работ в три ряда по шесть, все одного размера, прокрутки нет.
feature
Свои картинки в работу: доведено до конца
СДЕЛАНО и ПРОВЕРЕНО НА ЖИВЫХ ДЕНЬГАХ. Владелец просил: кидаю 2-3 картинки и говорю — сделай пост, где первая картинка это человек, со второй возьми одежду, а с третьей улицу. В Студии это уже работало, в заводе — нет, и по дороге нашлись четыре разные поломки (описаны отдельными задачами). Итог живой проверки: приложил фото своего синего дивана и фото зимней набережной у Эрмитажа, написал «поставь диван с первой на набережную со второй, заголовок — Уют выходит на улицу». Получил ровно это: тот самый диван на той самой набережной и заголовок слово в слово. Ролик тоже научился брать принесённые фото — и не только в первый кадр, а во все: иначе на втором кадре человек превращается в другого. Порядок картинок теперь часть задания и держится по всей цепочке.
feature
Ответ модели обрывался по лимиту токенов — работа умирала
ПОЧИНЕНО, есть проверка. Живой заказ: дизайнеру дали 3200 токенов, он не уложился, провайдер оборвал JSON на середине. Разбор упал, шаг пошёл на повтор — с тем же лимитом, то есть заведомо в тот же обрыв. Одиннадцать платных попыток, 19 руб наших денег, работа в провал. Теперь обрыв распознаётся отдельно от «модель ответила плохо»: читаем finish_reason и переспрашиваем один раз с удвоенным запасом, потолок 8000. Дизайнеру подняли стартовый запас до 4500. Написана проверка src/ask.test.ts на четыре случая, включая «не расти выше потолка».
feature
Цена шага была ниже себестоимости и душила повторы
ПОЧИНЕНО. Цена шага у нас работает и как потолок трат: тратить на шаг больше, чем берём за него, нельзя. Замысел поста стоил клиенту 6 руб, а обходился нам в 10-12 (четыре агента и круги критика) — значит повтор запрещался с самого начала, и первая же осечка убивала работу целиком. Раскладка выверена по живым работам: замысел 12, текст 2, картинка 6. Сумма для клиента прежняя, 20 руб.
feature
Принесённые картинки терялись по дороге на сервер
ПОЧИНЕНО. Моя же ошибка в новом выборе картинок: значение поля сбрасывалось ПОСЛЕ выбора и стирало только что записанный набор. В браузере список показывался, а форма уходила пустая — человек платил за результат без своих картинок и не понимал, почему. Сброс перенесён на открытие окна выбора. Добавлена страховка: если браузер не принял набор, человек видит «картинки не прикрепились», а не узнаёт об этом по списанным деньгам.
feature
Агенты сочиняли своё вместо указания клиента
ПОЧИНЕНО — это была главная причина «не то нарисовали». Человек написал «возьми мой диван и покажи на набережной, заголовок такой-то», а получил макро среза дерева с надписью «Дерево, которое переживёт три ремонта». Причина: крючок, копирайтер и дизайнер обращались с темой как с поводом для собственного креатива и вообще не знали, что у работы есть фотографии клиента. Теперь есть сквозное указание клиента: признак — человек принёс свои фото, значит режиссирует сам и его слова исполняются буквально. Указание уходит всем трём агентам и во ВСЕ круги правок (иначе второй круг стирал его), а в промте картинки стоит последним и перебивает выдуманную сцену. Без своих картинок всё работает как раньше — свобода агентам.
feature
Админка: вход под клиентом
СДЕЛАНО по просьбе владельца. Механика в ядре была с самого начала, но позвать её было неоткуда. В карточке человека — «Зайти под клиентом» с обязательной причиной (её требует ядро: просмотр чужого кабинета это доступ к переписке, файлам и деньгам). Сверху кабинета висит плашка «вы смотрите кабинет клиента» и рядом выход — искать его в меню чужого кабинета никто не станет. Свой ключ прячется в отдельную куку и возвращается при выходе: иначе каждый разбор выкидывал бы сотрудника из своей учётки. ПРОВЕРЕНО СКВОЗНЯКОМ: вошёл, админка закрылась (я уже не сотрудник), плашка на месте, вышел — снова собой, просмотр закрыт, запись с причиной в журнале.
feature
Админка: раздел «Производство»
СДЕЛАНО. Отвечает на вопрос «сервис работает или только кажется»: выход по форматам с долей провалов, шаги-виновники с живыми текстами причин и сравнение цены шага с себестоимостью. Последнее — не любовь к цифрам: перекос цены молча душит производство, и чтобы найти его сегодня, пришлось писать запросы руками. Теперь шаги дороже своей цены висят наверху красной плашкой. Время считается по заказу, а не по работе: updated_at меняется от любой правки и показывал «среднее время ролика — двое суток».
feature
Обход страниц встроен в ворота
СДЕЛАНО, и сразу поймал поломку. Обход открывает каждый экран по-настоящему, но в воротах его не было: ошибка в запросе конкретной страницы проходила 1235 зелёных проверок и попадалась только глазами — так и случилось с карточкой человека (500 из-за неверного имени колонки). Теперь: обход идёт ПОСЛЕ подъёма новой сборки (раньше проверял бы старую), роняет выкат при беде и откатывает сборку, и проходит админку глазами сотрудника — до сих пор она проверялась только отказом постороннему, то есть по существу не проверялась вовсе. 86 страниц на каждом выкате.
feature
Каталог моделей Kie вытащен, реестр расширен до 41
СДЕЛАНО по просьбе владельца. Список моделей у Kie отдаётся отдельным эндпоинтом (POST client/v1/model-pricing/page, по 100 записей, из России только через прокси) — обычного /api/v1/models у них нет. Сейчас там 447 записей: 65 видеомоделей, 49 картиночных, 20 музыкальных. Наш реестр знал 12. Завёл ещё 22: самая дешёвая картинка (0.4 руб себестоимости), фотореализм, детальная, живописная, правка по образцу, увеличение; ролики от 1.8 руб за секунду; липсинк втрое дешевле нашего аватара. Каждый ключ взят из каталога и сверяется на каждом выкате — короткие «правдоподобные» имена в API не работают, на этом уже горели. Лендинг теперь честно говорит «18 моделей видео и 19 картиночных»: числа он берёт из реестра сам.
feature
Выбор движка в студии, с ценой у каждого
СДЕЛАНО. Владелец просил «менюшку, где можно выбрать модели». У картинки десять движков (3–17 руб), у ролика семь (20–68 руб за пять секунд). Цена считается на сервере тем же кодом, что и списание, и переключение идёт ссылкой — иначе на кнопке осталась бы цена прежнего движка, то есть враньё ровно там, где человек решает, тратить или нет. Названия в меню короткие: «Самый дешёвый», «Кинематографичный» — имена вроде bytedance/seedance-2-fast человеку не говорят ничего.
feature
Очередь под нагрузкой: десять постов разом
ПРОВЕРЕНО НА ЖИВЫХ ДЕНЬГАХ. Десять работ встали за 361 мс, все десять дошли до готового, ноль провалов. Заказ в среднем 163 секунды, худший 224 — под нагрузкой, то есть очередь не деградирует. Одновременно в работе доходило до десяти, воркер держит три шага разом (это его настройка), очередь не забивалась, повторов не было. Написан инструмент tools/nagruzka.ts: ставит N работ залпом и показывает проходимость, время, деньги и шаги-виновники. Наши расходы на десять постов — 158.71 руб при цене 200 для клиента.
feature
Ролс не работал ни разу: пустая раскадровка
ПОЧИНЕНО, есть проверка. Линия передавала режиссёру пустой шаблон — промт превращался в «сделай ровно 0 сцен», и модель честно возвращала пустой список. Три платные попытки и провал. Шаблонов ролса в базе не оказалось вовсе, их не перенесли. Написал четыре кодом: «Проблема и решение», «Три причины», «До и после», «Показ товара» — это устоявшаяся форма, платить ИИ за её сочинение каждый раз незачем. Проверка следит, что раскадровка не пустая, ролик остаётся 15–35 секунд и в каждом шаблоне есть призыв.
feature
Озвучка не работала совсем: шла мимо прокси
ПОЧИНЕНО. ElevenLabs отвечает нашему серверу перехватом: снаружи «302 Moved», изнутри заглушка Cloudflare с кодом 403. Через тот же прокси, которым ходят модели, отвечает нормально — проверено запросом баланса. Озвучка стоит в середине линии: без неё не собирается ни ролик, ни ролс, ни аватар. Пришлось дописать в прокси-клиент двоичный POST: речь приходит файлом mp3, и сборка её в строку портит файл необратимо. Правка внесена в переносчик (tools/port-tts.py), а не в перенесённый файл — иначе следующий перенос молча её выбросил бы; ворота это и поймали.
feature
Кадры превращались в mp4 и ломали сборку
ПОЧИНЕНО. Служба рендера получала «frame-1.mp4» и не могла показать это как картинку. Дело оказалось не в имени: кадры укладывались с форматом «видео», канон видео задаёт частоту кадров, и приведение честно прогоняло png через ffmpeg, получая односекундный ролик. Теперь формат при укладке необязателен: заготовке канон не нужен. Заодно тип файла определяется по первым байтам, а не по формату работы — семь проверок на png, jpeg, webp, mp4, mp3 и на запасные пути.
feature
Логотип уходил рендеру путём вместо ссылки
ПОЧИНЕНО. В профиле лежит путь внутри нашего хранилища, а служба рендера принимала его за адрес и достраивала своим основанием — выходил localhost:3005, которого нет нигде. Кадры в том же запросе подписывались правильно, а логотип забыли. Теперь подписывается рядом с ними; не удалось подписать — идём без логотипа: ролик без картинки в углу лучше, чем провал всей работы.
feature
Работа показывалась готовой без результата
ПОЧИНЕНО — это было хуже неверной суммы. Ролс отдал шесть кадров и шесть реплик, рендер упал, приёмка не выполнялась, а человеку показали «Готово». Деньги при этом считались верно (только за сделанные шаги), но надпись врала: человек открыл бы работу и не нашёл ролика. Причина — исход «неполно» смешивал два случая: не вышел необязательный шаг (ролик есть, просто без музыки) и не вышел обязательный (главного файла нет). Теперь смотрим на результат: есть файл, ради которого работу заказывали, — работа состоялась; нет — «Не вышло», а заготовки сохраняются, они оплачены и пригодятся при повторе. Проверка расчёта дополнена этим случаем, всего 27 проверок.
feature
Ролс собран целиком и проверен глазами
ПРОВЕРЕНО. После четырёх починок ролс проходит все шаги с первой попытки: 25 секунд, 1080×1920, звук AAC, 38 МБ. Субтитры на месте и читаются («Провисшие пружины, пятна, царапины…»), сверху плашка бренда. Ролики Veo тоже проверены: два подряд за 3 минуты, 8 секунд со звуком, себестоимость 28.5 руб при цене 60 — как и решил владелец. Замечание на потом: логотип в углу мелкий и тусклый, почти не читается.
feature
Перерасход на отладке: 606 руб вместо согласованных 215
СКАЗАТЬ ВЛАДЕЛЬЦУ. Согласовано было десять постов и два ролика — около 215 руб наших расходов. Фактически ушло 606: посты 158.71, ролики Veo 57, и 390.58 на ролсы. Причина в том, КАК я чинил: каждая из четырёх поломок ролса вскрывалась только на следующем шаге, и каждый раз я запускал полный прогон заново — а он заново рисует шесть кадров (28.5 руб) и заново озвучивает. Тринадцать прогонов вместо двух. Как не повторять: при отладке линии повторять надо с упавшего шага, а не работу целиком — механика пересборки в ядре есть, я ей не воспользовался. Отдельно стоит сделать режим отладки, где готовые шаги берутся из прошлой работы.
feature
Veo Lite заведён: вдвое дешевле Fast
ПОПРАВЛЕНО по замечанию владельца. Он просил Lite, а я гонял Fast — просто потому, что Lite в реестре не было вовсе. По каталогу Kie: Lite 720p — 14.25 руб, Lite 1080p — 16.62, Fast 720p — 28.5. Завёл veo3_lite, проба в студии идёт теперь на нём (30 руб клиенту), Fast остаётся выбором (60 руб). ПРОВЕРЕНО ЖИВЬЁМ: ролик готов за 1.3 минуты, себестоимость ровно 14.25. Отдельно: я ошибся, сказав, что Lite не работает с картинками. Владелец поправил — работает, разница только в качестве. В каталоге у Lite есть и image-to-video, и reference-to-video; ограничение, на которое я сослался, — старая строчка в коде рабочего сервиса.
feature
Субтитры к видео: перенесены из рабочего сервиса
СДЕЛАНО. Владелец: «на проде это работает — берём у видео аудио, транскрибируем, делаем srt и накладываем. Почему у нас субтитров нет?». Ответ: их не переносили. В ядре субтитры были только у ролса, где текст и тайминги известны заранее (мы сами озвучивали). Ролик, который говорит чужим голосом — Veo, аватар, — оставался без подписей. Перенесено скриптом tools/port-subtitles.py и сверяется воротами повторным переносом: раскладка ASS, караоке по словам, плавное появление, перевод цветов, тайминги — всё дословно. Заменено только распознавание: у прода это OpenAI Whisper, а в ядре его ключа нет и напрямую регион закрыт. Взял ElevenLabs Scribe — ключ и путь через прокси у нас уже работают, и он даёт тайминги по словам, без которых караоке пришлось бы делить фразу поровну. ПРОВЕРЕНО на живом ролике Veo: распознал «Такой диван переживет три переезда» — ровно то, что модель произнесла, — и вшил с подсветкой по словам. Размер поднял с перенесённых 48 до 64: при 1080×1920 сорок восемь теряются на нижней кромке. Настройки берутся с завода, те же самые, что у ролса: включены, шрифт, размер, цвет, обводка, положение, капс. Девять проверок на разбивку потока слов на строки: не длиннее семи слов, не дольше 3.5 секунд, пауза и точка рвут строку.
feature
Нагрузка была раздельной, а не смешанной
УТОЧНЕНИЕ для владельца: он спросил, делал ли я 10 постов и 2 ролика ОДНОВРЕМЕННО. Нет. Посты шли одним залпом (10 из 10 готовы), ролики Veo — отдельным прогоном позже, ролсы — ещё отдельно. Смешанной нагрузки, где в очереди одновременно и короткие работы, и длинные, не было. А именно она и интересна: длинные ролики занимают места воркера (их всего три) и могут держать короткие работы в ожидании. Это следующий прогон.
feature
Ключи взяты со стейджа, поддержка в Телеграме включена
ПОПРАВЛЕНО по замечанию владельца: «почему просишь токены и ключи, которые уже есть все на стейдже или в проде, бери всё оттуда». За сессию я дважды попросил лежащее рядом. Токен телеграм-бота вообще стоял в .env самого ядра под именем CZ_ALERT_BOT_TOKEN вместе с CZ_ALERT_CHAT_ID. Сверил списки переменных ядра, стейджа и прода и добрал всё, чего не хватало: SUPPORT_TELEGRAM_TOKEN (бот Контент Завода со стейджа), OPENAI_API_KEY (тот самый, об отсутствии которого я говорил на субтитрах), VK_APP_ID и VK_APP_SECRET, YANDEX_CLIENT_ID и YANDEX_CLIENT_SECRET, APIFY_API_TOKEN. Секрет вебхука сгенерирован. Оповещения поддержки переведены на канал тревог, который в ядре уже настроен — заводить под них вторую пару переменных было незачем. Приём обращений идёт клиентским ботом @Contetn_zavod_MyVision_pub_bot, вебхук поставлен на /api/hooks/support/telegram с секретом. Проверено, что стейдж этого бота не слушает (вебхука не было, опроса в коде нет), — публикация им не сломалась. ПРОВЕРЕНО СКВОЗНЯКОМ: сообщение принято вебхуком, нитка завелась в общей очереди с именем и темой, ответ поддержки записан в переписку. Отправка ответа в Телеграм вернула «chat not found» — это не поломка: я подставил chat_id от другого бота, а бот не может писать человеку первым, пока тот сам ему не написал. Обработано правильно: ответ сохранён, причина в журнале. ОСТАЛОСЬ ПРОВЕРИТЬ ВЛАДЕЛЬЦУ (одно сообщение): написать боту @Contetn_zavod_MyVision_pub_bot что угодно. Обращение должно появиться в админке в разделе «Поддержка», а ответ оттуда — прийти обратно в Телеграм.
feature
Проверки больше не шлют владельцу ложные обращения
ПОЧИНЕНО по вопросу владельца: он увидел в Телеграме три «обращения в поддержку» и спросил, реальные ли это клиенты. Нет: два пришли из tools/support.check.ts, который гоняется в воротах на КАЖДОМ выкате, третье — из моей проверки вебхука. Причина — моя ошибка проектирования: оповещение стояло внутри доменной функции написать(), а её дёргают и проверки. Ложное оповещение опаснее отсутствующего: на третий раз их перестают читать, и вместе с ними теряется настоящее обращение. Перенёс в слой входа — форма кабинета и вебхук мессенджера, куда проверки не ходят. Страховка в самой функции: молчит под VITEST и CZ_CHECK. В tools/deploy.sh выставлен CZ_CHECK=1. ПРОВЕРЕНО: следующий выкат прошёл полностью, и в Телеграм от него не пришло ничего.
feature
готовS13 · С8а · Заводы4/4

Цель: Конвейеры производства возвращаются как первоклассная сущность. Итог: то, чем сегодня пользуются через /factory/*, работает на новом ядре.

Завод как сущность
тематика, форматы, расписание, режим, план; у бренда несколько заводов
src/factories.ts: завод с тематикой, форматами, расписанием (дни недели, сколько в день, чем заполнять) и тремя режимами. Создаётся ВСЕГДА в ручном, что бы ни просили. Перевод в «сам» требует причины и попадает в аудит, без расписания не разрешается, остановленный завод не переводится. Снятие остановки сбрасывает счётчик провалов, иначе следующая же неудача снова остановит. Расписание проверяется: девятого дня недели не бывает, пятьдесят выпусков в день — авария со счётом.
feature
Планировщик заводов поверх заказов
в ручном режиме не производит ничего, в авто — строго по плану
собратьПлан раскладывает выпуски по дням расписания и считает смету, ничего не производя: план создаётся неутверждённым, между «предложили» и «списали» всегда человек. Темы не выдумываются — это отдельная работа помощника и отдельные деньги. НАЙДЕНО И ПОЧИНЕНО: планировщик не смотрел на режим завода. Завод «только по кнопке» с утверждённым планом запускал работы сам — восемь работ там, где должно быть ноль. Теперь план едет только в режиме «сам».
feature
Интерфейс завода
настройка, план, лента произведённого
Раздел «Заводы» в кабинете: список с режимом и остановкой, карточка завода с настройкой (название, форматы, дни, сколько в день, чем заполнять), сменой режима с причиной, сборкой и утверждением плана со сметой, лентой произведённого. НАЙДЕНО: кириллица в шаблонных именах полей (`формат-${ф}`) — форма не доносила ни одного отмеченного формата. Проверка «на проводе только латиница» такие имена пропускала: видела подстановку и шла дальше. Теперь смотрит на литеральную часть шаблона, у форматов появились латинские ключи для провода (КЛЮЧ_ФОРМАТА). Проверено 131 имя.
feature
Сборка завода помощником
основа для «Лия собирает кастомные заводы»
src/factory-assistant.ts: помощник читает бриф бренда и предлагает завод (название, зачем, форматы, дни, сколько в день) со сметой в месяц. Один вызов модели, не пять «уточняющих». Предложение НИЧЕГО не создаёт: ждёт согласия в черновике, завод появляется по кнопке и всегда в ручном режиме. Промах модели (заполнять форматом, которого нет у завода) чинится молча, мусор в ответе — отказ, а не завод из пустоты. tools/factory-assistant.check.ts — 17 проверок на подставном отправителе, без трат. Живой прогон ждёт согласия владельца: он стоит денег (около рубля).
feature
В РАБОТЕS14 · С8б · Студия4/5

Цель: Разовые обращения к моделям поверх общего кошелька и очереди; семь мест списания схлопываются в одно

Единый вход вместо семи мест списания
студия работает через заказы и кошелёк ядра
Разовая генерация — такой же заказ, как всё остальное: тот же кошелёк, та же очередь, та же приёмка, тот же номер для поддержки. Формат работы «студия», линия из двух шагов (сделать и проверить). Цена берётся из каталога вариантов по реестру провайдеров, а не задаётся отдельным числом — иначе цена в студии и цена вызова разъедутся молча. Каталог закрытый: «любая модель по выбору» это способ заплатить за результат, который некуда деть.
feature
Общая библиотека результатов
сделанное в Студии можно взять в пост или ролик
Библиотека показывает всё готовое бренда в одном месте: и разовое из студии, и то, что вышло из работ. Разделять незачем — человек ищет «ту картинку с диваном», а не «результат шага отрисовки заказа №41». Пока файлов нет, показывается пусто, а не список заказов.
feature
История, повтор, пресеты
повторить с теми же настройками
История студии с кнопкой «Повторить»: прошлый запрос подставляется в поле, ничего не отправляется само. Настройки (пропорции, разрешение, длительность) стоят прямо у промта и уезжают провайдеру как есть. ПЕРЕДЕЛАНО по замечанию владельца: студия — прямой прокид в Kie, а не мини-линия. Убран шаг приёмки, который я добавил из-за общего юнит-теста: судить «то ли получилось» здесь некому, человек просил ровно это. Тест теперь знает про исключение и объясняет почему. Помощь с описанием — единственное место, где участвует модель кроме генерации. Только по кнопке, с ценой на ней (1 ₽), переписывает текст человека подробнее и ничего не выдумывает сверх сказанного. Пресеты не делал.
feature
Долгие генерации переживают уход со страницы
через общую очередь
Разовая генерация идёт через очередь как обычный заказ, поэтому уход со страницы её не трогает: воркер доведёт, результат появится в истории и библиотеке. На экране это сказано прямо — «можно закрыть страницу». Дополнительного кода не потребовалось: свойство досталось от того, что студия не заводит своего пути в обход ядра.
feature
Улучшения по ответам на В-18
ждёт решения владельца
feature
планS15 · С8в · Вирус и Лия0/4

Цель: Оба перестают быть островами: работают через ядро, Лия получает СММ-Центр

Перенос Вируса
встраивание по решению В-19: источник идей для плана плюс кнопка в создании
feature
Лия через ядро
сегодня дёргает генерацию напрямую мимо всех проверок
feature
Лия в СММ-Центре
помогает и управляет, а не только заводами
feature
Границы полномочий Лии
готовит и предлагает, человек подтверждает траты и публикации; ждёт В-20
feature
В РАБОТЕS16 · С8г · Публичный API1/5

Цель: Полноценный API для заводов, СММ-Центра и Студии: ключи, квоты, версия, единый формат ошибок

Ключи и квоты
выдача, отзыв, лимиты по тарифу
feature
Покрытие заводов, СММ-Центра, Студии
то же ядро, те же правила и потолки
feature
Идемпотентность и единый формат ошибок
заголовок ключа повтора, коды ошибок
Идемпотентность готова; единый формат ошибок — вместе с публичным API в С8г
feature
Вебхуки наружу
события о готовности и публикации
feature
Документация из кода
не отдельный текст, который устареет
feature
планS17 · Уроки с навесов0/6

Цель: Перенести в Контент Завод то, что проверено живыми генерациями на проекте навесов.

🔴 Ударения в озвучке по словарю
Движок сам угадывает русское ударение и на профессиональных словах ошибается: сказал «арО́чный» вместо «А́рочный». Лечится знаком U+0301 сразу после ударной гласной, ставится словарём перед синтезом. Признак, что движок принял: дорожка заметно длиннее. Реализация: lib/stress.ts проекта навесов.
backend
🔴 Озвучка только прямым ElevenLabs через прокси
Через Kie их TTS падает с internal error на любых параметрах: проверено четырьмя вариантами (с языком и без, свой голос и стоковый). Прямой вызов api.elevenlabs.io отдаёт mp3 сразу, но с российского адреса приходит 302, поэтому только через прокси.
backend
🔴 Поле образца у gpt-image-2 это input_urls
С image_urls модель молча игнорирует образец и рисует другого человека: в кадр вместо аватара попала посторонняя женщина. Слаги: gpt-image-2-text-to-image и gpt-image-2-image-to-image.
backend
🔴 Формула вживления человека в настоящее фото
Промпт по-русски, фото объекта ПЕРВЫМ, лицо вторым, прямой запрет «первое фото не меняй, только добавь человека» и требование естественности по свету и цвету. Английский промпт с теми же требованиями даёт похожий, но чужой объект.
backend
fail у Kie не значит, что денег не взяли
Задача отчиталась fail с пустым результатом, а в кабинете видео есть и кредиты списаны. Сверять по балансу и кабинету, а не по статусу. Повтор отправки разрешать только когда баланс не сдвинулся.
backend
Стоп-темы: проверка после генерации
Запрет в промпте дырявый, нужна проверка готового текста. Осторожно: \w в JS не покрывает кириллицу, окончания писать через \p{L}.
backend
планS18 · С12 · Режиссёр роликов0/13

Цель: ЧЕРНОВИК НА ПОДУМАТЬ, этапы не утверждены. Ролик собирается не склейкой двух клипов, а режиссёрским листом: эпизоды 2-5 сек с типами (лицо / вставка / сплит / моушен-карточка), слои, титры, пословные субтитры. Упор владельца: моушен-графика кодом (экраны телефона, UI-мокапы, терминал, барабаны выбора, счётчики), видео - лишь часть. Цель по себестоимости 300-500 руб за ролик. Движок Remotion (лицензия бесплатна: 1 сотрудник) + ffmpeg на подхвате. Прописка не решена: внутрь ядра, если успеем, иначе отдельный сервис и подключение по API.

Развилка: прописка Режиссёра
Решение владельца 12.08: если ядро cz-core успеваем доделать - Режиссёр встраивается внутрь; если нет - делаем отдельный сервис и подключаем по API. Решать в момент готовности ядра, формат листа и рендерер от этого не зависят.
idea
Развилка: лицо в кадре
В эталонных разборах (Nick Saraev, drcintas) говорящая голова занимает 10-39% хронометража и держит доверие. У завода лица нет. Варианты: аватар OmniHuman (13,5 руб/сек - это +200-400 руб на ролик, съедает весь бюджет) либо форматы без лица (только моушен + b-roll + субтитры). От ответа зависит набор шаблонов форматов. Решает владелец.
idea
Формат режиссёрского листа (JSON-контракт)
Основа: timeline -> tracks -> clips (проверенная схема Shotstack), сверху наш слой смысла: роль эпизода (хук / доказательство / оффер / CTA), тип кадра (лицо / вставка / сплит / моушен-карточка), привязка к строке сценария и к пословным субтитрам. Схема валидируется, версионируется, лист неизменяем (правило ядра: артефакт неизменяем, пересборка добавляет версию). Эталоны для обратной проверки - два разбора рилсов от владельца в scratchpad.
feature
Рендерер director-render: лист -> mp4
Сервис по образцу rols-render (/opt/rols-render, pm2, 127.0.0.1:3100). Remotion как основа + ffmpeg на нарезке исходников и финальном кодеке. Обязательно: очередь и лимит параллелизма - на cfru 8 ядер, 23 ГБ, load уже ~2.3, там же cz-core и rols-render; рендер 30 сек 1080x1920 это минуты полной загрузки CPU. Лицензия Remotion: бесплатна, у компании 1 сотрудник (порог - 4).
feature
Библиотека блоков: пилот 10 анимаций + каталог с оценкой
Моушен-графика кодом по категориям: типографика (караоке-субтитры, маркер, счётчик, печать), UI-мокапы (телефон, браузер, терминал, чат, загрузка файла, барабан выбора), схемы (стрелки, лучи-связи, воронка, до/после, чек-лист), переходы (whip pan, zoom punch, глитч, маска, шторка), акценты (обводка, указатель, шейк, вспышка), раскладки (сплит 50/50, лицо+экран, сетка, зум на деталь). База: Magic UI - 150+ компонентов под MIT (мокапы iPhone/Safari/Android, macOS-терминал, animated-beam, number-ticker, typing-animation), переносятся в Remotion почти как есть. Каждый блок параметризуем (цвет, шрифт, текст, длительность, направление) + превью + теги для режиссёра. Каталог со страницей оценки ок/не ок.
12.08 две партии на живом каталоге https://myvisionagency.ru/lab/rezhisser/. ИТОГО ПРИНЯТО 15 БЛОКОВ. Партия 1 (7 из 12): барабан выбора, счётчик падения, терминал, экран телефона, загрузка файла, субтитры по словам, связь лучом. Партия 2 (8 из 8, без единого отказа): метрика растёт, заявка в переписке, заявки стопкой, календарь публикаций, поиск с подсказками, таймлайн монтажа, карточка товара, гео и охват. ВЫЧЕРКНУТО НАВСЕГДА по решению владельца: маркер с меткой, было и стало, чек-лист, переход рывком, переход ударом - сами приёмы не нужны плюс дёшево выглядели. КРИТЕРИЙ ОТБОРА выведен: берём интерфейс и механику процесса, не берём оформленные слайды с текстом и переходы как самостоятельный эффект. ПЛАНКА КАРТИНКИ: многослойные тени и глубина, стекло, свет под объектами, пружины вместо линейных кривых, детализированные интерфейсы, дыхание кадра. МЕХАНИКА: цикл 4,5-5,5 сек, петля замкнута, мёртвого хвоста нет. Записано в память feedback-motion-blocks-taste. ВАЖНО на следующий шаг: превью сделаны CSS-анимациями для браузера, в Remotion их надо переписать как функцию от номера кадра (useCurrentFrame, interpolate, spring) - CSS-анимации при покадровом рендере не воспроизводятся; хореография и тайминги переносятся один в один.
feature
Каталог шрифтов и выбор под бренд
Проверено 12.08: в Google Fonts 1942 семейства, из них 294 с кириллицей (130 sans, 67 serif, 63 display, 19 mono, 15 рукописных), все OFL, коммерция разрешена. @remotion/google-fonts уже стоит в rols-render. Разметить теги (характер, читаемость в мелком кегле, пара заголовок+текст) и выбирать КОДОМ по бренд-киту, не запросом к ИИ (правило: не платить ИИ за статику).
feature
Пословные субтитры (караоке)
В эталонах субтитры идут группами по 2-4 слова крупным кеглем с подсветкой текущего слова. Тайминги брать из ElevenLabs with-timestamps (память content-factory-subtitles-whisper-own-tts: Whisper по собственной озвучке врёт). Для чужого видео (разбор трендов) - WhisperX, точность <100 мс.
feature
Режиссёр-ИИ v1: сценарий -> лист
ИИ пишет ТОЛЬКО лист, ничего не рисует и не считает (compiled-ai). Вход: сценарий, реально доступный материал, бренд-кит, шаблон формата. Три формата на старт: гайд, новость, оффер. Плюс критик листа КОДОМ: длина эпизода, доля говорящей головы, есть ли хук в первые 2 сек, не повторяется ли анимация подряд.
feature
Добытчик материала (resolver вставок)
По каждому эпизоду решает источник в порядке цены: моушен-графика кодом (0) -> материалы клиента (0) -> сток Pexels/Pixabay (0, без атрибуции, но кэшировать у себя и не хотлинкать) -> генерация Kie (15-60 руб). Ставка владельца: основную красоту даёт код, видео - лишь часть.
feature
Фабрика трендов: пилот 10 разборов
Разбор чужого рилса в такой же лист: PySceneDetect (границы склеек) + WhisperX (пословные тайминги) + Vision-модель (описание кадра и тип: лицо/вставка/сплит). Источник данных: Apify, Instagram ~1,5$ за 1000 результатов, TikTok ~1,7$ - 50 трендов дешевле доллара. Итог: лист-эталон + страница оценки ок/не ок. Своего источника нет: файлы владельца взяты из чужого ТГ-канала как образец цели.
research
Расширение до 100 блоков и 50 трендов
Только после оценки пилота владельцем. Гнать объём до подтверждения направления запрещено - решение 12.08.
feature
Режиссёр длинных роликов: докрутить форматы
ИДЕЯ ВЛАДЕЛЬЦА, в работу не брать без его отмашки. Часть уже начата — надо докрутить. Суть: красивого видео в 2026 мало. Зритель должен понимать, о чём ролик и зачем ему досматривать. Персонаж, который просто стоит, красиво говорит и объясняет мораль, — это не контент и не сериал. Что делать: доработать форматы режиссёра под ДЛИННЫЕ ролики — не набор красивых кадров, а история с завязкой и переменой. Владелец ссылается на разбор из поста и на «скилл сценариста рилс», который он давал: перед работой поднять этот материал и сверить наши форматы с ним. Не начинать, пока владелец не покажет исходник поста и скилл: пересказ идеи здесь неполный, в оригинале была видеочасть.
feature
Проверка драматургии до отрисовки: три вопроса
ИДЕЯ ВЛАДЕЛЬЦА, в работу не брать без его отмашки. Перед тем как рисовать ролик, сценарий обязан ответить на три вопроса: 1. Что происходит с героем прямо сейчас? 2. Что изменится к концу? 3. Если убрать ИИ-эффект, останется ли здесь хоть какая-то история? Третий — главный: он отсекает ролики, которые держатся только на красивой генерации. Ставить это ДО отрисовки, там же, где сейчас работает критик: круг правок текстом стоит рубли, а перерисовка сцен — сотни. Ответы на три вопроса имеет смысл показывать человеку в сценарии: он тогда видит, что правит, а не абзац красивых слов. Формулировки владельца сохранены дословно — при доработке сверяться с ними, а не с пересказом.
feature
В РАБОТЕS19 · С13 · Вид сервиса61/64

Цель: Разводная страница и три продукта на равных, вид уровня higgsfield и mage.space. Задача целиком: docs/ЮАЙ-ЗАДАЧА.md

Изучить образцы глазами: higgsfield.ai и mage.space
Нужен браузер, текстом уже смотрели и этого мало. Смотреть: лендинг, разделы, кабинет, экран генерации, как показаны примеры работ, как показаны цены и лимиты, наведения и анимации. Снять: палитру, шрифты, сетку, радиусы, приёмы карточек. Результат: разбор в docs/ЮАЙ-ЗАДАЧА.md, а не в чате.
Оба образца сняты глазами, в том числе кабинеты под входом владельца. Разбор в docs/ЮАЙ-ЗАДАЧА.md: палитра, шрифты, радиусы и кегли замерены кодом с живых страниц, 10 приёмов к переносу, что не берём. 15 снимков в docs/obraztsy/. Главное: цена на кнопке до нажатия (higgsfield), кредиты переведены в понятные штуки, меню группами (mage), инструменты лентой а не пунктами, закрытое тарифом с замком.
task
План внешнего вида и обсуждение с владельцем
Пять решений, которые нельзя принимать по ходу (перечислены в docs/ЮАЙ-ЗАДАЧА.md): что видит человек после входа, как уживаются три продукта, что делать с пунктами под тарифом, одна тема или две, публичные страницы свои или на рабочем домене. Код начинать только после «ок».
Макеты согласованы владельцем 18.08.2026 словами «огонь, супер, мне очень нравится». Лендинг myvisionagency.ru/lab/cz-core-lending/, кабинет myvisionagency.ru/lab/cz-core-vid/. Закрыты все пять развилок плюс стилистика: палитра из логотипа (чёрный, лайм c7f52b, белый, техно-капс, гранж-мазки через feTurbulence). Три захода: первый макет забракован по лендингу и мельтешению, второй дал четыре варианта лендинга и выбор «живой соцсети», третий свёл всё в фирменный стиль. Отдельно поймано правило: лайм не бывает полупрозрачным, под альфой на чёрном он даёт болото.
task
Разводная страница личного кабинета
Сейчас человек после входа падает сразу в СММ-Центр, и сервис выглядит как один СММ-Центр. В рабочем сервисе это /dashboard: плитки цифр, три карточки продуктов (Контент Заводы, Студия, СММ-Центр), отдельный призыв на тест генерации. ЗАВОДЫ ОТДЕЛЬНЫЙ ПРОДУКТ, не раздел СММ-Центра.
Сделано и выложено на стенд, проверено глазами в двух темах и на телефоне. Данные собирает обзорБренда() в src/screens.ts одним походом в базу. Числа с разрезом: заводов работает из настроенных, в производстве и сколько застряло, готово за неделю с «+N к прошлой», потрачено за месяц со средней ценой штуки (при нуле работ показываем остаток, а не «0 ₽ за штуку»). Карточки продуктов разные: у Заводов очередь выпусков с неподвижными обложками и состоянием словами дела («Собирается», «Ждёт вас», «Опубликовано»), у Студии форматы из каталога с числом инструментов, у СММ-Центра подключённые площадки с индикатором живости и три настоящих числа (четвёртое из макета, «новые отклики», не показываем: «Входящие» не перенесены). Зовущий блок на Студию. Живое видео только в ленте последних работ и только у видимых плиток. Пустые состояния объясняют, чего не хватает. Проверки на обзорБренда добавлены в tools/screens.check.ts.
task
Шапка и боковое меню по новой структуре
Шапка: Главная, Тарифы, Инструкции, Блог, Обновления, Кабинет, кнопка «Создать завод», баланс, вход в ЛК. Убрать: Партнёрка, Все ИИ агенты, Наши боты. Оставить: Настройки, ИИ-ассистент, Поддержка, Лия-архитектор. Грабли прода: баланс и бейдж тарифа наезжали на пункт «Кабинет». Тариф держать в теле дашборда.
Сделано и выложено на стенд, вид проверен глазами в двух темах и на телефоне. Меню группами по продуктам (Обзор, Контент Заводы, Студия, СММ-Центр, Компания); раскрыта группа текущего продукта, а на разводной первая — иначе меню было пятью заголовками над пустотой. Мёртвых пунктов не осталось: «Собрать завод» ведёт в список заводов, где и живёт форма, «Конкуренты» и «Поддержка» видны с пометкой «скоро», но не нажимаются (раньше все три давали «не найдено»). Добавлены «Настройки компании» и «Права и доступ» — страницы работают, а входа в них не было. /b/{бренд} больше не 404, уводит на разводную. ЗАМКИ ПЕРЕДЕЛАНЫ: считаются в src/tarif.ts по настоящей таблице тарифов, а не написаны буквами. С «ИИ-аватара» и «Конкурентов» замок снят — в биллинге за них не берут денег; остались «Тренды» и «Аналитика». Называется самый дешёвый ПЛАТНЫЙ тариф: у «Корпоративного» цена ноль, и он вылезал первым. Без подписки не закрыто ничего — её нет у 718 организаций из 722. Проверка tools/tarif.check.ts (13 проверок) вписана в гейт. Меню телефона переписано под ту же структуру: внизу Обзор, Заводы, Студия, Публикации, остальное за «Ещё» теми же группами.
task
Создать завод: отдельный поток
В ядре есть только настройка готового завода. В рабочем сервисе это /factory/create и главная кнопка шапки. Пересекается с задачей «Мастера создания заводов» в С11.
Сделано и выложено, проверено глазами в обеих темах и заведением настоящего завода. Своя страница /b/{бренд}/factories/new вместо формы внизу списка; на неё ведут кнопка «Создать завод» в шапке, пункт меню «Собрать завод» и призыв с разводной, когда заводов ещё нет. Два входа: собрать самому и помощник по брифу (помощник переехал сюда же со списка). ГЛАВНОЕ — СМЕТА ДО НАЖАТИЯ: на форме видно цену каждого формата за штуку, а внизу живая сумма «столько-то ₽ в месяц за N шт., в среднем столько-то за штуку» и честное «на счету хватит примерно на N мес.» либо «на месяц не хватает, пополните до запуска». Формула сметы вынесена в src/prices.ts (сметаКонвейера) и теперь одна на страницу сборки и на помощника — раньше помощник считал у себя, и две копии разошлись бы молча. Расписание задаётся сразу при заведении: раньше завод создавался без единого дня, человек уходил уверенным, что всё готово, а завод молчал. Завод по-прежнему появляется в режиме «только по кнопке» и ничего не тратит. Список заводов очищен от формы: там теперь список и кнопка. Побочно починена подсветка меню: «Мои заводы» и «Собрать завод» живут в одном разделе, и меню врало, где человек находится — Каркас принимает имя текущего пункта. Смоук вырос до 54 проверок.
task
Тест генерации
РЕШЕНО ВЛАДЕЛЬЦЕМ 19.08: проба платится из подарочных 100 ₽, генерация видео на Veo 3.1 Fast, цена клиенту 60 ₽. Проверено: у Kie это отдельный эндпоинт /api/v1/veo/generate (не общий jobs/createTask), модель veo3_fast, ролик 8 секунд, опрос через /api/v1/veo/record-info, себестоимость $0.30 = ~28 ₽ по курсу 95. Маржа 2,1x. В рабочем сервисе та же модель стоит клиенту 90 ₽ — проба дешевле нарочно. В реестре моделей ядра Veo пока НЕТ, добавляется этой задачей.
ГОТОВО И ПРОВЕРЕНО ЖИВЫМ РОЛИКОМ. Экран /b/<бренд>/test-video: одно поле, три готовых замысла, выбор кадра, кнопка «Сделать за 60 ₽». Модель veo3_fast в реестре, вызов своей веткой в src/kie.ts (у Veo отдельный адрес /api/v1/veo/generate и своё состояние успеха числом). Ролик 8 секунд 1080×1920 со звуком лежит у нас, открывается и скачивается. ДВА ЖИВЫХ ПРОГОНА НАШЛИ ДВЕ НАСТОЯЩИЕ ПОЛОМКИ, обе денежные. (1) РАЗБОР ОТВЕТА: Veo кладёт ссылку в data.response.resultUrls, а общий createTask — строкой в resultJson. Первый ролик сняли, деньги провайдеру ушли, ссылку мы прочитали как пустую, шаг посчитали неудавшимся, второй заход остановил потолок трат — работа кончилась провалом при готовом оплаченном ролике. Разбор теперь понимает обе формы, закреплено юнит-тестом на настоящих ответах (src/kie.test.ts, 5 проверок). (2) ЦЕНА ШАГОВ (важнее): резервировали 60 ₽, а списывали 17 ₽ — цена заказа бралась из каталога студии, а цены шагов из линии, где стоит опорная. При себестоимости 28,5 ₽ мы дарили клиенту работу и уходили в минус на КАЖДОЙ студийной генерации, молча: все проверки смотрели на цену заказа, а она была правильной. Теперь цены шагов приводятся к цене заказа (остаток от округления — на последний платный шаг). Это общее правило, оно закрывает и второй случай: пункт плана с ценой, зафиксированной при утверждении. Записано как INV-084, держится тремя проверками в tools/orders.check.ts. ГЕЙТ ЗЕЛЁНЫЙ ЦЕЛИКОМ. Заодно научил проверку имён моделей различать модели со своим адресом: у Veo имя вызова veo3_fast, а в каталоге Kie она лежит как veo-3-1.
task
Поиск конкурентов
В рабочем сервисе /dashboard/competitors. В ядре нет совсем. Отдельный раздел, не внутрь СММ-Центра.
task
Студия: сверить инструменты с рабочим сервисом
В рабочем сервисе 14 инструментов: Обзор, Проверка на вирусность, Фотосессия, Видео, Изображения, Музыка, Озвучка, Аватар, Нарезка рилсов, Анимация, Удаление фона, Брендирование, Банк идей, Библиотека. В ядре /studio, /studio/[вид], /studio/library. Сверить поштучно и решить, что переносим, а что закрываем.
СВЕРКА ЗАКРЫТА решением владельца 19.08: «Студию переноси» — переносим все шесть инструментов, которых в ядре нет. Сверка показала, что платного спроса за 8 месяцев они почти не дали (одно списание на 40 ₽), но решение владельца важнее статистики: витрина должна выглядеть не беднее рабочего сервиса. Работа разбита на шесть отдельных задач.
task
Поддержка и ИИ-ассистент со стороны клиента
В ядре есть только сторона сотрудника. Поддержка в рабочем сервисе это всплывашка в меню.
СДЕЛАНО обе стороны, проверено живым сценарием на стенде: клиент написал — сотрудник увидел в очереди — ответил — у клиента загорелась точка. КЛИЕНТ: раздел /b/<бренд>/support, слева свои обращения, справа переписка или форма нового с четырьмя готовыми поводами (пустое поле «тема» отсеивает часть людей). Показываем ВСЕ его обращения, а не по текущему бренду: человек пишет один раз, а брендов у него может быть пять. СОТРУДНИК: /admin/support, очередь «сначала те, где ждут нас», ответ, закрытие ответом или без ответа, ссылка на карточку человека. МЕТКИ: точка «вам ответили» гаснет при открытии, метка сотрудника — тоже при открытии, а не при ответе (посмотреть и отложить бывает, а «новое» после прочтения — вранье очереди). Закрытие без ответа точку НЕ ставит: молча закрытая переписка не должна выглядеть как ответ. Писем не шлём — у клиента остаётся ровно три повода получить от нас письмо. Миграция 0060 (unread_client + три индекса), src/support.ts, tools/support.check.ts — 21 проверка в гейте, включая «чужое не открывается по прямой ссылке» и «дописать в чужую нельзя». ГЛАЗАМИ ПОЙМАНО ТРИ: точка была не видна в свёрнутой группе меню (сигнал, которого не видно, — это не сигнал; теперь точка и на заголовке группы); форма ответа восстанавливала текст после отправки и сотрудник мог отправить то же дважды; список обращений читался ДО снятия метки, и точка горела рядом с перепиской, которую человек в этот момент читает. ИИ-ассистента в этой задаче нет — он ждёт решения по В-20.
task
Публичные страницы: решить и сделать
Главная, Тарифы, Инструкции, Блог, Обновления. Сейчас шапка ядра уводит на 24contentzavod.ru намеренно. Развилка: свои страницы в ядре или развод по адресам в nginx. Связано с задачей С11 «Развести адреса на nginx».
РЕШЕНО И СДЕЛАНО. Развилка «свои страницы в ядре или развод по адресам» закрыта в пользу развода: витрина (главная, тарифы, инструкции, блог, обновления, оферта, политика, условия, контакты, документация) остаётся на рабочем домене. Она не пишет ни строки клиентских данных, и переносить её в ядро — это одиннадцать страниц работы ради нуля пользы. Решение уже стояло в docs/ЧТО-ОСТАЛОСЬ.md, сверился и держусь его. ЧТО СДЕЛАНО В ЯДРЕ: найдена дыра — ссылок на оферту и политику не было НИГДЕ. Человек заводил кабинет и платил деньги, ни разу не увидев, по каким условиям. Теперь ссылки стоят в двух местах: у кнопки «Завести кабинет» и под кнопкой «Пополнить». Согласие у кнопки, а не галкой выше: галку ставят не читая, а сюда взгляд падает в момент решения. Адреса собраны в одну константу ПРАВОВЫЕ (app/_части/каркас.tsx) — когда витрина переедет, править одно место. Держит tools/pravovye.check.ts в гейте: проверяет и что ссылки в исходниках на месте (иначе их снесут при правке вёрстки и никто не заметит), и что страницы ещё отвечают 200 — витрина живёт отдельно от нас, её могут переставить. ЧТО ОСТАЛОСЬ НА ДЕНЬ ПЕРЕКЛЮЧЕНИЯ: витрина ссылается на СТАРЫЙ кабинет в пяти местах (/auth/login, /auth/register, /dashboard, /settings, /factory/create в Header.tsx и Footer.tsx). Раньше дня переключения их трогать нельзя — прод живой. Список с файлами и признаком готовности дописан в docs/ДЕНЬ-ПЕРЕКЛЮЧЕНИЯ.md отдельным шагом 4.1.
task
Перенести подарок за регистрацию
На проде работает: триггер handle_new_user (supabase/migrations/20260805_company_on_signup.sql) берёт features->>balance_bonus тарифа free, кладёт на баланс и пишет транзакцию «Подарок за регистрацию». Живое значение в БД = 100 ₽ (в миграции 055 лежит 300, база главнее). В ядре этого нет: есть только промокоды с бонусом. Начислять надо через кошелёк на двойной записи, а не правкой баланса — иначе повторится баг админской правки счёта, где начисление писалось без заказа и поддержка не могла вернуть деньги. Нужна защита от многоразовой регистрации на новые почты. Лендинг давит на «100 ₽ на счёт после регистрации», так что без этого переноса лендинг обещает несуществующее.
Сделано и проверено на живой регистрации: новый кабинет видит 100 ₽ и «хватит на работы» вместо красной плашки «Счёт пуст». Сумма в core.tariffs.gift_rub (миграция 0056), не в коде. Начисление через кошелёк на двойной записи, идемпотентность держит база (провайдер + организация), сбой начисления не мешает войти. Путь клиента дополнен проверкой подарка.
feature
Лендинг по согласованному макету
Макет согласован: myvisionagency.ru/lab/cz-core-lending/, исходник C:\tmp\cz-core-lending.html. Перенести в код все девять блоков рабочего лендинга с текстами прода: герой со стеной контента и реакциями, три сегмента (ведут на /for/sellers, /for/services, /for/creators), четыре шага, девять возможностей, студия с форматами и моделями, витрина работ вкладками видео-карусели-посты, тарифы живыми ценами из базы, шесть вопросов гармошкой, финал, подвал. Стилистика: чёрный, лайм #c7f52b, белый, техно-капс, гранж-мазки через feTurbulence. ЛАЙМ НЕ БЫВАЕТ ПОЛУПРОЗРАЧНЫМ: под альфой на чёрном даёт болото, подложки нейтральные. Цены за штуку на лендинге НЕ показывать, упор на регистрацию и подарок 100 ₽. Подарок зависит от задачи «Перенести подарок за регистрацию» — без неё лендинг обещает несуществующее. Контент: 15 роликов /landing-v2/videos/proof/ и 12 картинок /landing-v2/images/proof/ с прода, стоп-кадры вынуты ffmpeg. Перед публикацией пересчитать на живой базе цифры 733 работы и 170 заводов.
СДЕЛАН И ОТКРЫВАЕТСЯ на /lp. Все девять блоков согласованного макета в коде: герой со стеной контента и всплывающими реакциями, три сегмента, четыре шага, девять возможностей, студия с форматами и моделями, витрина работ вкладками, тарифы, вопросы гармошкой, финал, подвал. Классы оставлены именами макета — так проще сверять «то же самое или нет», открыв макет рядом. ЧИСЛА ЖИВЫЕ, КАК И ТРЕБОВАЛА ЗАМЕТКА К МАКЕТУ («перед публикацией пересчитать на живой базе»): 745 работ, 170 заводов, 5 сетей, 18 моделей, подарок 100 ₽ — всё считается при открытии страницы. Цены тарифов из core.tariffs. И ГЛАВНОЕ — УБРАНЫ ОБЕЩАНИЯ, КОТОРЫХ ЯДРО НЕ ДЕРЖИТ. В макете стояли тексты рабочего сервиса: «15+ моделей видео» (в ядре 4), «10+ моделей картинок» (3), «10 соцсетей напрямую» (5), «ещё восемь площадок», Kling/Hailuo/Runway/Midjourney/Flux в списке моделей (их нет), «клонирование голоса» в тарифе Бизнес (не сделано). Всё это заменено на живые числа из реестра и на подписи тарифов, собранные из limits в базе: поменяли тариф — поменялась витрина. Обещание, которого нет внутри, человек обнаруживает после оплаты. Кадры роликов скопированы к нам (public/lp/kadr), чтобы витрина не зависела от лабораторной папки на myvisionagency. Стили макета перенесены целиком и заперты под .лендинг: у витрины свой чёрный фон и свои размеры, пускать это в кабинет нельзя.
feature
Догнать перенос: прод ушёл вперёд
Сверка переноса 19.08 не сходится и блокирует штатную выкладку: в ядре на 100 ₽ больше, расходится у одного человека. Причина не в коде - рабочий сервис живёт, перенос делали 16.08, накопилось 39 людей, 3 готовых работы, 58 денежных операций. Лечится прогоном переносов (у каждого есть режим показа без записи и флаг --делать). Пока не догнали, выкладка идёт с SKIP_GATE=1, и это плохо: гейт данных выключен целиком, а не только сверка. Команда: cd /var/www/cz-core && set -a && . ./.env && set +a && npx tsx tools/sverka-perenosa.ts
РАЗОБРАНО И ЗАКРЫТО решением владельца: финальный перенос людей и балансов делается перед релизом на прод, а не сейчас — расхождения появляются потому, что прод живой и люди в нём работают, пока мы переносим. Разбор дал картину крупнее одного человека: на проде БАЛАНС ЭТО САМОСТОЯТЕЛЬНОЕ ЧИСЛО, а не сумма операций — у 326 человек из 538 он не сходится с их же журналом (у 310 больше, у 16 меньше). Там его правили напрямую. Перенос берёт именно число баланса и подгоняет ядро записью выравнивания; пересчитывать по журналу нельзя — половина клиентов увидит не ту сумму. Сухой прогон подтвердил: финальный перенос сам запишет −100 ₽ и остатки сойдутся, ключ идемпотентности включает посчитанную сумму. СВЕРКА ПЕРЕДЕЛАНА: проверки разделены на «беда» (потеряно или задвоено — переключаться нельзя) и «отстало ⟳» (прод ушёл вперёд, догоняется переносом, выкладку не останавливает). Заодно исправлена проверка заводов: она сравнивала общее число в ядре и в проде и краснела от любого нашего же завода — показательного, пробного, из проверки; то есть ровно тогда, когда стендом пользовались. Теперь сверка зелёная, 28 проверок, и штатный гейт больше не стоит красным из-за живого прода. В ДЕНЬ-ПЕРЕКЛЮЧЕНИЯ.md дописано: признак готовности — ни одной строки с ⟳.
Сверка переноса блокирует штатную выкладку: в ядре на 100 ₽ больше, расходится у одного человека. РАЗОБРАНО 19.08, прогоном переносов НЕ лечится. Это roman-kotov@bk.ru: в проде 710 ₽, в ядре 810 ₽. 16.08 перенос выровнял ему остаток записью «выравнивание остатка при переносе». После этого баланс в проде уменьшился на 100 ₽ БЕЗ ЕДИНОЙ ОПЕРАЦИИ В ЖУРНАЛЕ: последняя запись в public.transactions у него от 09.08, неперенесённых операций нет вообще. То есть на проде число правили мимо журнала — ровно то, против чего в ядре сделан кошелёк на двойной записи. РЕШЕНИЕ ЗА ВЛАДЕЛЬЦЕМ: считать правильным число прода (тогда в ядро нужна запись выравнивания с причиной) или число ядра (тогда искать, кто и когда правил баланс руками на проде). Пока не решено, выкладка идёт мимо этого шага гейта — остальные 58 проверок гоняются отдельно.
bug
Вход и регистрация: привести к фирменному виду
Страница входа осталась от прежней вёрстки: форма прижата в левый верхний угол, много пустоты, фирменного оформления нет. Это первый экран клиента. Палитра уже фирменная, нужна композиция: карточка по центру, логотип, обещание подарка за регистрацию рядом с кнопкой.
ДОДЕЛАНО: страница входа по приглашению приведена к тому же виду, что вход по паролю. Была замечена при живой проверке — осталась от прежней вёрстки, и человек, пришедший по приглашению, видел сервис, собранный из двух разных вёрсток. Тот же разворот: слева знак, надпись «вас позвали в кабинет», заголовок и три строки про совместную работу (своё имя в журнале, права от владельца, общий кабинет), справа карточка с формой.
feature
Настройки компании: разделы, каталог товаров, айдентика
Владелец разобрал прежнюю страницу настроек: блоков много, плохо читаемы, одно поле на логотип и фото товара, цвета кодом, и вопрос «откуда взялось обращение на вы». Разобрано по коду: часть полей работала, обращение слушала одна линия из пяти, водяной знак не читал никто.
Сделано и выложено, проверено глазами и заведением настоящего товара. ШЕСТЬ РАЗДЕЛОВ со своим боковым списком: Компания, Товары, Аудитория, Голос бренда, Айдентика, Площадки; у каждого написано, на что он влияет в производстве. КАТАЛОГ ТОВАРОВ (миграция 0057): принадлежит бренду, завод выбирает из него; артикул, цена, ссылка на маркетплейс, плюсы, минусы, фото, наличие. Работа помнит свой товар (works.product_id), профильДляРаботы подставляет его описание и фото вместо общего — подмена в одном месте, линии про каталог не знают. Минусы товара уходят в запреты. Завод раскладывает товары по кругу вместе с форматами, пункт плана помнит товар (миграция 0058). ГОЛОС доехал до всех линий: голосСтрокой = тон + обращение, обращение проверяется на приёмке от двух совпадений. ВОДЯНОЙ ЗНАК теперь настоящий: ffmpeg накладывает логотип на готовую картинку. ЛОГОТИПОВ НЕСКОЛЬКО с формой и назначением, логотипДля() выбирает нужный. ШРИФТ уходит дизайнеру карусели. ЦВЕТА выбираются пипеткой, а не вписываются кодом. Проверки: products.check.ts (24) и обращение в brand-profile.check.ts, обе в гейте.
feature
🔴 Пять экранов не сохраняли ничего: действие замыкало функцию
Найдено при проверке сохранения товаров у завода. Внутри страницы жила вспомогательная функция назад(...), и все действия звали её в redirect(назад(...)). Серверное действие замыкает на себя всё, что использует, Next пытается это сериализовать и падает с «Functions cannot be passed directly to Client Components». Страница при этом ОТКРЫВАЕТСЯ: ошибка только при отправке формы.
Починено на пяти экранах: карточка завода (настройка, режим, сборка и утверждение плана), контент-план, публикации, права и доступ, страница вида в Студии. То есть не работали утверждение плана, публикация, выдача прав и заказ разовой работы. Ни типы, ни смоук этого не ловили: типы довольны, страницы отвечают 200. Заведена проверка tools/server-actions.check.ts — читает исходники и ищет ровно этот случай, вписана в гейт.
bug
Разбор владельца: убрать ерунду из настроек, добавить рубрики и призыв
Владелец прошёл по свежим настройкам: цена в разделе компания, «чем вы лучше других», непонятные галки про сайт и телефон, нечитаемая кнопка, «что беспокоит покупателя» для монтажной пены, шрифт непонятно зачем. Просил подумать с позиции СММщика крупного бренда и селлера.
Проверено по коду, а не по памяти, и исправлено. УБРАНО: цена из раздела компания (она у товара); шрифт из айдентики — применить его сегодня некуда, модель его не соблюдает, свой рендер есть только у Ролса и шрифта не принимает, субтитров ядро не делает вовсе. ПЕРЕФОРМУЛИРОВАНО: «чем вы лучше других» → «почему выбирают вас»; «что беспокоит» → «что мешает купить» (сомнения и возражения); «кто покупатель» → «кто покупает и зачем». ДОБАВЛЕНО «КУДА ВЕДЁМ»: никуда / сайт / карточка товара / личные сообщения, ссылка подставляется настоящая, призыв уходит копирайтеру. Заменяет две противоречивые галки: ведём на сайт — сайт в текстах разрешён сам. Первым комментарием не умеем, и это написано на экране: Upload-Post не поддерживает. ДОБАВЛЕНЫ РУБРИКИ с долями (раздел «О чём говорим»): планировщик раскладывает их по весам на месяц вперёд и чередует, пункт плана помнит рубрику (миграция 0059), копирайтер видит её вместе с темой. Есть готовые наборы для товара и для услуг. ПОЧИНЕНО: main a.кнопка наследовала цвет от родителя — белый текст на лайме на всех кнопках-ссылках кабинета. Проверено вживую: товар заведён, выбран заводом, план собран — доли соблюдены (обзор 6, польза 4, отзывы 2), товар в каждом пункте. Проверки: каталог и рубрики 28, профиль 23, смоук 54.
feature
Слой завода в интерфейсе: позиционирование с наследованием от бренда
Владелец: «ты придумываешь настройки с нуля, а не берёшь наработки текущего завода». Разобран прод: 120 колонок в factories, компания и ограничения дублируются в каждом заводе. В ядре трёхслойная модель с наследованием была с самого начала (src/factory-profile.ts), но в интерфейс не выводилась.
Экран factories/{завод}/about: тема конвейера, аудитория, тон и обращение, свои запреты, «о чём не пишем», свои рубрики, предмет для не-товарных заводов. У каждого поля подписано, что стоит у бренда — пустое поле значит «как у бренда». Внизу «что в итоге получит производство»: сведённый профиль. Проверено вживую — тон и аудитория перекрыты заводом, предмет взят от бренда, запретов стало 3 (1 брендовый + 2 заводских, сложились), рубрики заменены заводскими. Рубрики добавлены в СхемаПозиционирования: заводские заменяют брендовые целиком, а не дополняют.
feature
Субтитры на завод: шрифт, размер, цвет, положение, пресеты
Перенос с прода (subtitles_font, size, color, outline, position, short_video, avatar) плюс пресеты Жёлтый/Неон/Мятный/Чистый. Требует правки службы рендера rols-render: она принимает субтитры {text,startSec,endSec}, но стиль в ней зашит намертво. Ядро сейчас шлёт пустой список. Сюда же вернётся фирменный шрифт — он про субтитры конвейера, а не про марку.
Сделано и проверено настоящей сборкой ролика. СЛУЖБА РЕНДЕРА научилась стилю субтитров: шрифт, кегль, цвет, цвет и толщина обводки, положение, капс — раньше вид был зашит в компонент. Обводка штрихом поверх заливки, а не четырьмя тенями: на крупном кегле тени дают рваные углы. ЯДРО собирает субтитры из реплик озвучки — тот же текст, что слышит человек, и те же тайминги; шаг озвучки стал держать текст и длительность рядом со звуком. НАСТРОЙКА НА ЗАВОДЕ, как на проде: экран factories/{завод}/video, шесть готовых стилей с прод-названиями и живой предпросмотр на макете кадра. ПОЙМАНО ПРИ ПЕРЕНОСЕ: у Poppins нет кириллицы, а на проде он в пресете «Чистый» — русские субтитры вышли бы квадратами; заменён на Inter, в выборе оставлены только шрифты с кириллицей. И субтитр наезжал на подпись сцены — увидел на первой пробной сборке, теперь подпись поднимается. Машинные проверки этого не видят: рендер отрабатывает успешно. ОГРАНИЧЕНИЕ, написанное на экране: субтитры только на Ролс — у готового ролика от модели нет ни таймингов, ни текста реплик.
feature
Плашка-хук на завод
hook_overlay_enabled и hook_overlay_style с прода: шрифт, размер, цвет, обводка, положение, длительность.
Сделано и проверено настоящей сборкой ролика. Крупная надпись поверх первых секунд с притемнением кадра, уходит плавно за 0,4 с — резкая смена читается как склейка. Текст берётся из подписи первой сцены, своего сервис не выдумывает: лишний вызов модели ради заголовка, который уже есть. Настройка на заводе рядом с субтитрами, с живым предпросмотром и длительностью 1–6 с.
feature
Стиль контента и персонажи на завод
content_style (продающий, обучающий, развлекательный, разговорный, кинематограф) и characters/characters_mode с прода.
Сделано. Стиль подачи (продающий, обучающий, развлекательный, разговорный, истории, кинематограф) и описание того, кто в кадре. Задаётся на заводе, перекрывает бренд, читает режиссёр роликов и общий контекст модели.
feature
Свои промты завода — только сотрудникам
scriptwriter_prompt, smm_prompt, designer_prompt с прода. Решение владельца: клиенту не показывать, сломает себе генерацию. Нужны нам, чтобы подкручивать конкретный завод руками.
Сделано по решению владельца: блок виден только сотрудникам, и промты принимаются только от них — поле на клиентском экране не рисуется, но форму можно отправить и мимо экрана. Добавка идёт ПОСЛЕ наших указаний и не отменяет их: формат, запреты и «не выдумывать числа» остаются. Сейчас читается добавка сценаристу (линия Ролса); дизайнерская и копирайтерская сохраняются и подключатся со своими линиями — написано прямо на экране.
feature
Логотипы и цвета: бренд хранит, завод выбирает
На проде post_logo_url/post_brand_colors и carousel_logo_url/carousel_brand_colors раздельно. Сейчас в ядре назначения заданы на бренде; надо дать заводу выбирать из брендовых.
Сделано. Логотипы и палитра принадлежат бренду, завод выбирает: какой логотип на картинки, какой в ролики, какими цветами работать. Не выбрал — берётся брендовое и едет за брендом при правке. Выбор переносит назначение на выбранный логотип и снимает с остальных, поэтому логотипДля возвращает выбранное заводом, а линиям про заводы знать не нужно. Чужой путь не подставляется: поле показывает только брендовые логотипы, но форму можно отправить мимо экрана, поэтому выбор проверяется на сервере. Экран завода переименован в «Как выглядит» — там логотип с цветами, субтитры и плашка-крючок. Проверки в factory-profile.check.ts: 22 (было 17).
feature
Реквизиты и юридические ограничения на уровне организации
ОТЛОЖЕНО РЕШЕНИЕМ ВЛАДЕЛЬЦА 19.08: не делаем сейчас. «У нас такого не было, таких вопросов не было, столько ещё делать — готовить новую фишку прям щас не хочется, но точно надо оставить под неё место в будущем». Вернуться ПОСЛЕ переключения на прод, как отдельную фичу. МЕСТО В АРХИТЕКТУРЕ УЖЕ ОСТАВЛЕНО: уровень организации (core.orgs) свободен — сейчас на нём нет ни одной настройки, всё живёт на бренде и заводе. Когда понадобится, сюда лягут: реквизиты, дисклеймеры, маркировка рекламы (ЕРИР), кто согласует публикацию, возрастные ограничения. Наследование то же, что у бренда и завода: организация задаёт, бренд уточняет. Ничего переделывать не придётся — только добавить слой.
feature
Студия: проверка на вирусность
ИИ разбирает ролик и говорит, что мешает ему разойтись: хук в первые секунды, темп, субтитры, звук. В рабочем сервисе 40 ₽ за разбор, результат нигде не хранится — в ядре хранить как работу, иначе человек платит второй раз за то, что уже смотрел.
СДЕЛАНО И ПРОВЕРЕНО ЖИВЫМ РАЗБОРОМ. Расход 0,24 ₽, с клиента 40 ₽ (как в рабочем сервисе: 40 ₽ до 30 секунд, дальше рубль за секунду, считается по НАСТОЯЩЕЙ длине файла). РАЗДЕЛЕНИЕ ТРУДА: длина, размер кадра, вертикальность, наличие звука и средняя громкость меряются КОДОМ через ffprobe и уезжают модели готовыми числами с прямым запретом их пересчитывать. Модель судит только то, что измерить нельзя: цепляет ли хук, ясен ли посыл, на какой секунде свайпнут, аккуратно ли вплетён товар. Без этого модель пишет «ролик секунд двадцать» там, где восемь, и это число живёт в отчёте как правда. Проверка на живом ролике показала, что модель действительно СМОТРИТ видео: описала пылинки в лучах солнца и медленный наезд на подушку — это в точности наш Veo-ролик. Поставила 2/10, «свайпнут на первой секунде», три конкретных правки. Строго и по делу. В ядро добавлена возможность показать модели видео (src/ask.ts): раньше туда уходил только текст. У Gemini выключены рассуждения — они съедают предел токенов, эти грабли уже ловили. Разбор показывается на экране работы человеческим видом: балл, вердикт, что поправить в первую очередь, где бросят смотреть, разбор по частям. JSON-файл тоже сохраняется — его можно забрать.
feature
Студия: фотосессия товара
Комплект кадров товара в едином стиле, в рабочем сервисе 100 ₽ за комплект. В ядре уже есть каталог товаров с фото и брендстиль — фотосессия берёт исходник и стиль оттуда, а не спрашивает заново.
ГОТОВО И ПРОВЕРЕНО ЖИВЫМ КОМПЛЕКТОМ. Четыре кадра с одной фотографии товара: каталожный на чистом фоне, в обстановке, крупный план фактуры, живая сцена с человеком. Диван на всех четырёх узнаваемо ТОТ ЖЕ — форма, цвет и фактура ткани сохранены, а это главное: похожий диван в карточке товара замечает не клиент, а его покупатель. Расход 19 ₽ (4 × 4,75), с клиента 100 ₽ — списание сошлось. ДВА ЖИВЫХ ПРОГОНА ДО ЭТОГО ПРОВАЛИЛИСЬ И НАШЛИ ДВЕ ПОЛОМКИ. (1) Провайдеру уходил ПУТЬ в нашем хранилище вместо адреса — Kie отвечал «Your media file is unavailable», четыре кадра подряд в отказ. Денег это не стоило (задача не создаётся), но искалось бы долго: теперь `нарисовать` сама проверяет образцы и называет ошибку прямо, закреплено юнит-тестом. Ссылку берём заново на каждый кадр: подпись живёт 15 минут, а комплект рисуется минут восемь — одной ссылки хватало бы впритык. (2) СЕРЬЁЗНЕЕ: у всех четырёх кадров кириллические роли («каталог», «обстановка», «фактура», «в деле») схлопывались в ОДНО имя файла — очистка до латиницы не оставляла ничего, и все получали общее «file». Второй кадр падал с «resource already exists», два оплаченных кадра пропадали. Починено в двух местах: ролям фотосессии заданы явные имена, а для любой будущей русской роли без перевода вместо общего «file» берётся короткий отпечаток самой роли — новая роль больше не уронит работу молча. Пять юнит-проверок в гейте.
feature
Студия: нарезка рилсов
Длинное видео на короткие вертикальные куски. Резать по смыслу, а не по секундам: PySceneDetect на границы склеек уже разбирался в задачах Режиссёра.
ОТМЕНЕНО решением владельца: он перестал оплачивать сторонний сервис, на котором держалась нарезка, и тот сейчас не работает. Просил убрать — говорил об этом дважды, и оба раза это терялось у меня между сессиями. Кода нарезки в ядре не было вовсе. Убрано обещание из ленты обновлений (v1.1.1, блок «Нарезка видео»): человек читал его и шёл искать функцию, которой нет. Записано в память, чтобы не всплывало снова.
feature
Студия: удаление фона
Прозрачный фон у картинки товара. Дешёвая утилита, но без неё фотосессия и брендирование упираются в чужой фон.
ГОТОВО И ПРОВЕРЕНО. Recraft через Kie: расход 0,48 ₽, клиенту 5 ₽. Результат — PNG 1080×1350 с настоящей прозрачностью (pix_fmt rgba), проверил подложив цветной фон: края чистые. Имя модели выяснено бесплатно: в каталоге Kie она «recraft-remove-background», а API принимает «recraft/remove-background» и требует поле image. Ошибки имени и полей у Kie не создают задачу и не стоят денег — разведка обошлась в ноль. ВАЖНАЯ ДЕТАЛЬ: файл кладётся БЕЗ приведения к канону. Приведение переводит картинку в jpg, а jpg не умеет прозрачность — то есть ровно та работа, ради которой инструмент нужен, потерялась бы на последнем шаге, и заметил бы это клиент. Локальный rembg (как в рабочем сервисе) не ставил: это питон плюс модель на 180 МБ на общий сервер, где и так живут cz-core, rols-render и заводы, ради операции за полтинник копеек.
feature
Студия: брендирование
Логотип и палитра на готовую картинку. В ядре это УЖЕ работает внутри производства (src/watermark.ts кладёт логотип бренда на работу) — задача в том, чтобы дать это отдельной кнопкой для своих файлов, переиспользуя тот же код, а не написав второй.
СДЕЛАНО и проверено картинкой. Инструмент «Логотип на картинку» в разделе Изображения: выбираешь свою картинку, получаешь её же с логотипом бренда в правом нижнем углу. Переиспользует src/watermark.ts — тот самый код, что работает внутри производства, второго не писал. ПО ДОРОГЕ СДЕЛАН ВЫБОР КАРТИНКИ-ОСНОВЫ, которого не было вовсе: варианты с исходником («Ролик из картинки») висели в списке с заглушкой «выбор появится следующим шагом» и выключенной кнопкой. Теперь галерея своих работ прямо в форме — заодно ожил и ролик из кадра. НАЙДЕНА И ИСПРАВЛЕНА НАСТОЯЩАЯ ПОЛОМКА ВОДЯНОГО ЗНАКА: в фильтре ffmpeg был перепутан порядок входов scale2ref, и вместо логотипа ужималась сама работа — кадр 1080×1350 превращался в 52×65 пикселей. ffmpeg отрабатывал успешно, файл получался валидный, ошибок ноль. Код не выполнялся с самого переноса: галка «водяной знак» выключена у всех брендов, то есть поломка ждала первого, кто её включит. Теперь наложение проверяет, что размер кадра не изменился, а в гейте стоит tools/watermark.check.ts — четыре проверки на настоящих картинках: размер, знак в правом нижнем углу, нетронутый остальной кадр, ширина знака в восьмую часть кадра. ЦЕНА 3 ₽ — на подтверждение: генерации здесь нет, наш расход ноль, но бесплатный заказ ядро не пропускает (резерв требует сумму больше нуля), а трогать денежное ядро ради одной кнопки я не стал.
feature
Студия: банк идей
Сохранённые промпты. Осторожно: в рабочем сервисе под этим именем лежит НАШ внутренний каталог на 144 записи с оценками, а клиентских записей три штуки с 15 февраля. Решить, что именно показываем клиенту — свои сохранённые или общую библиотеку удачных.
СДЕЛАНО. Развилка «свои сохранённые или общая библиотека» решена так: и то, и другое в одном списке, свои сверху. Основание — данные рабочего сервиса: под именем «Банк идей» там лежал НАШ каталог на 144 описания (126 живых, все с русским текстом, категории, счётчик применений — сверху то, что брали по 90 раз), а клиентских записей было три штуки с 15 февраля. Ценность именно в общей библиотеке, её и перенёс; возможность сохранить своё добавил, иначе «банк» это просто чужие тексты. Перенесено 126 описаний вместе со счётчиком применений (tools/perenos-idey.ts, идемпотентный). Отключённые (18 штук) не брал — их выключали осознанно. Экран /studio/ideas: свои описания сверху, ниже общие с фильтром по категориям (Кино, Товар, От первого лица, Превращение, Клип, АСМР…). Длинные описания свёрнуты до трёх строк — в каталоге они по три-четыре абзаца, сотня таких карточек нечитаема. «Взять в работу» кладёт текст в черновик студии и отправляет на нужный экран: ничего не заказывает и не тратит денег, человек правит под себя и запускает сам. На экране генерации добавлена кнопка «Сохранить в банк идей» — без неё банк не наполняется. Миграция 0061: core.prompt_bank, brand_id NULL = общая. RLS: общие видят все, свои — только владелец бренда, вставлять можно только своё. Счётчик применений растёт ТОЛЬКО у общих — иначе порядок в общем списке накручивается своими записями. Проверка tools/ideas.check.ts в гейте, 12 проверок на границу между общим и своим. Гейт зелёный. ВИД ГЛАЗАМИ НЕ ПРОВЕРЕН: браузер отвалился посреди работы, страницы отвечают 307 на гостя (то есть не падают), но скриншот сделаю при первой возможности.
feature
Поддержка: один поток из Телеграма, МАКСа и кабинета
РЕШЕНО ВЛАДЕЛЬЦЕМ 19.08 (ответ на В-23): «надо чтобы тг работал и макс и чат поддержки как сейчас. пишут, мне приходит уведомление в админку и в чате Лия». То есть: три входа — Телеграм, МАКС, кабинет — сходятся в ОДНУ очередь обращений ядра. Ответ сотрудника из админки уходит обратно тем же каналом, которым пришёл вопрос. Уведомление о новом обращении: счётчик в админке плюс сообщение в рабочий чат, где Лия. Клиенту писем по-прежнему не шлём — у него ровно три повода получить от нас письмо. Сегодня в ядре готова кабинетная часть (раздел у клиента, очередь у нас, 21 проверка). Осталось: вебхук бота ТГ на ядро, вход из МАКСа, доставка ответа наружу, уведомление в чат. Основание: все 8 живых обращений рабочего сервиса — гостевые из Телеграма, у людей нет кабинета; это основной вход, а не запасной.
СДЕЛАНО. Кабинетная поддержка работала и раньше, не хватало входа из мессенджеров: человек, у которого что-то не работает, пишет туда, где ему привычно, и такое сообщение оседало в чужой личке — в очереди его нет, ответить некому, клиент решает, что его игнорируют. Теперь один поток. Обращение из Телеграма или МАКСа заводит нитку в той же очереди, что и кабинетное; ответ поддержки уходит обратно в мессенджер; на первое сообщение человеку приходит подтверждение, что он попал куда надо. НАЙДЕНА ПОЛОМКА ПО ДОРОГЕ: очередь соединялась с людьми внутренним JOIN, а у обращения из мессенджера человека может не быть вовсе — он ещё не регистрировался. Такое обращение было бы принято, записано и невидимо: худший вид поломки, когда никто не узнаёт, что клиента потеряли. Соединение стало левым, в очереди видно канал и как человек подписан в мессенджере. Механика канально-независимая: миграция 0066 добавила нитке канал, обратный адрес и имя из мессенджера, сообщению — номер для защиты от повторной доставки (мессенджеры шлют повтор, если не получили подтверждения). Вебхук один на оба канала, отвечает 200 всегда: на любой другой ответ мессенджер шлёт обновление по кругу, а через сутки отключает доставку. Секрет в заголовке или параметре — иначе ручку найдут перебором. ПРОВЕРЕНО: вебхук принял сообщение, нитка завелась с каналом, именем и темой из первых слов. Семь проверок в vitest на то, что ломается молча: обращение видно в очереди, второе сообщение продолжает ту же переписку, повтор не задваивает, после закрытия заводится новая нитка. НУЖНО ОТ ВЛАДЕЛЬЦА, чтобы включить: токен бота поддержки в SUPPORT_TELEGRAM_TOKEN (или разрешение использовать публикационный Contetn_zavod_MyVision_pub_bot — смешивать роли я не стал), чат для оповещений в SUPPORT_ALERT_CHAT, секрет в SUPPORT_HOOK_SECRET. Пока переменных нет, канал спит, а кабинетная поддержка работает как прежде: обещать клиенту канал, которого нет, нельзя.
feature
Витрина: правки лендинга по замечаниям владельца
СДЕЛАНО. Убраны счётчики работ и заводов — числа низкие и играли против нас. Шапка выровнена по центру (логотип у левого края и кнопки у правого расползались на широком экране). Мазок в финале стал белым: тёмный на чёрном пропадал, и фраза читалась как «Соцсети сами себя». Плашка «Скоро добавим больше» убрана. В витрине работ теперь все 15 роликов вместо шести. ЧИСЛА МОДЕЛЕЙ ИСПРАВЛЕНЫ ПО ФАКТУ КАТАЛОГА: было «4 модели видео» из реестра ядра, стало 18 видео и 19 картинок — из живого каталога студии, где 49 активных моделей. Заголовок студии переписан под оффер: «49 моделей ИИ в одном окне». УБРАНА ЛИШНЯЯ МЕХАНИКА: «Озвучка ElevenLabs» → просто «Живая озвучка», имя провайдера клиенту не показываем. У ИИ-аватаров убрано «запишете тридцать секунд» — у нас другая механика, аватар делается из одной фотографии.
feature
Витрина: страницы под нишу (селлеры, услуги, эксперты)
СДЕЛАНО, /for/sellers, /for/services, /for/creators. Тексты перенесены из рабочего сервиса как есть — они писались под живые запросы этих людей и уже работают в поиске; придумывать заново значит терять проверенное. На каждой: свой герой, боль тремя строками, шесть форматов ниши, примеры работ именно этой ниши, четыре шага, подсказка какой тариф под задачу, вопросы ниши. Цены НЕ переносились: в самом файле прода стояло предупреждение «ЦЕНУ СЮДА НЕ ПИСАТЬ», появившееся после того, как лендинги показывали устаревшие тарифы. Берём из базы. Для поиска: свои title и description, один H1, canonical, микроразметка вопросов — по ней поисковик показывает ответы прямо в выдаче.
feature
Витрина: блог с переносом статей
СДЕЛАНО: /blog, /blog/<статья>, /blog/category/<раздел>. Все 26 опубликованных статей читаются из той же базы — они уже собирают поиск, терять их нельзя. НАЙДЕНО ПРИ ПЕРЕНОСЕ: все 26 обложек БИТЫЕ. Ссылки ведут на tempfile.aiquickdraw.com — временное хранилище Kie, которое картинки удалило (проверено: 404 на каждой). То есть блог прода прямо сейчас показывает статьи с пустыми картинками. Обложки заменены типографскими карточками: всегда на месте, единообразны, ничего не стоят. ЕЩЁ: категории заведены (5 штук), но НИ ОДНОЙ статье не проставлены — блок разделов пуст. Раскидать 26 статей по разделам стоит: это структура сайта для поиска. SEO на статье: canonical, open graph с датой публикации, разметка Article и хлебных крошек, время чтения, счётчик просмотров продолжает счёт прода, ссылки на соседние статьи.
feature
Витрина: тарифы, обновления, карта сайта
СДЕЛАНО. /tariffs — цены и содержимое из базы, разметка Product с предложениями, пять вопросов про оплату с разметкой FAQ. Отдельно объяснено то, что путают: подписка открывает разделы, работы списываются по факту. /updates — журнал обновлений перенесён целиком (10 версий), с разделением «стало удобнее» и «починили»: это разные новости, и человеку важно видеть, что поломки чинят. /sitemap.xml — собирается из живых данных: 33 адреса, статьи подтягиваются сами. /robots.txt — кабинет, админка и вход закрыты от обхода, витрина открыта. КОРЕНЬ ТЕПЕРЬ ПОКАЗЫВАЕТ ВИТРИНУ, а не переадресацию на вход: по переадресации робот индексировал форму входа. Вошедшего по-прежнему уводим в его бренд.
feature
Витрина: сквозная шапка, значок сайта, картинка ссылки
СДЕЛАНО. Шапка и подвал вынесены в оболочку витрины: раньше на главной работала одна навигация, на блоге другая — это были два разных сайта под одним доменом. Теперь одна на всех страницах. ЗНАЧОК: был только SVG, и часть браузеров его не берёт. Добавлены favicon.ico (16/32/48), значок для телефона 180, значки 192 и 512 и манифест — сайт, сохранённый на домашний экран, получает имя и иконку. КАРТИНКА ССЫЛКИ: на рабочем сервисе в мете указана /og-image.png, а файла НЕТ — 404. То есть все репосты сервиса в Телеграм и ВК идут без картинки, с января. Здесь картинка нарисована и отдаётся; у статьи блога вместо неё встаёт её обложка.
feature
Витрина: правовые страницы, контакты, переадресации старых адресов
СДЕЛАНО. /privacy, /terms, /offer сняты с ЖИВОГО рабочего сервиса дословно (tools/snyat-pravovye.py) — это документы, на которые ссылается договор, переписывать их нельзя. Оформление наше, текст их. /contacts — реквизиты ИП, ИНН, банк, адрес плюс разметка организации с ИНН и адресом: по ней ответные машины понимают, что за сайтом живая компания. ПЕРЕАДРЕСАЦИИ на день переключения: /pricing → /tariffs, /auth/login → /login, /auth/signup и /auth/register → регистрация, /contact-sales → /contacts. Именно 301, а не привычный для Next 308: 308 не описан в документации Яндекса, а он основной источник. Без этого всё, что накопил поиск по этим адресам, ушло бы в 404. НЕ ПЕРЕНОСИЛ: инструкции (/docs). Их экраны сняты со старого кабинета, а кабинет переделан — подсказка с чужой картинкой хуже, чем ссылка наружу. Записано в книгу переключения отдельным пунктом.
feature
ГЕО: сайт под поиск через ИИ
СДЕЛАНО. Владелец уточнил: ГЕО — это не города, а поиск через ИИ. /llms.txt — выжимка для ответных машин: что за сервис, цены, тарифы, разделы, до 40 статей со ссылками. Собирается из живых данных, потому что выжимка, устаревшая на полгода, вреднее её отсутствия: машина процитирует её как факт. robots.txt переписан вручную: явно пускаем GPTBot, OAI-SearchBot, ClaudeBot, PerplexityBot, Google-Extended, YandexAdditional и ещё восемь — разрешение не должно зависеть от того, как очередной робот трактует звёздочку. Там же строка со ссылкой на llms.txt. РАЗМЕТКА: Organization и SoftwareApplication на всех страницах витрины, у статьи добавлены дата правки, раздел, ключевые слова, число слов и время чтения, картинка. Карта сайта: 41 адрес против 9 на рабочем сервисе — у него в карте нет НИ ОДНОЙ статьи блога.
feature
Метрика: счётчик перенесён на витрину и вход
СДЕЛАНО. Счётчика в ядре не было вовсе — на переключении статистика оборвалась бы: визиты, источники, поисковые запросы и цели начались бы с нуля. Взят тот же номер 106102698, история продолжится. СТОИТ НЕ ВЕЗДЕ: витрина и вход — там реклама и поиск. В кабинет намеренно не ставил: вебвизор пишет запись экрана, а на экране кабинета чужие материалы, брифы и переписка с поддержкой; отправлять это наружу ради цифры посещаемости не нужно. Номер берётся из переменной окружения; на стенде её нет, поэтому тестовые заходы не мешаются с боевой статистикой. Проверено с временно включённой переменной: скрипт грузится, window.ym — функция, на /admin счётчика нет. В книгу переключения добавлен пункт.
feature
Блог: пять новых статей и живые картинки
СДЕЛАНО, лежат черновиками. Владелец решил, что тексты пишу сам, без модели — так и сделано. Пять разборов: цена ведения соцсетей по позициям (9 600 знаков), выбор модели для видео (7 900), почему ролики не досматривают (7 400), контент-план на месяц (8 400), ИИ-аватар (6 800). Жанр разбора, без «мы предлагаем», цены и лимиты — из живого каталога и тарифов, не выдуманные. КАРТИНКИ: 11 штук нарисовано (5 обложек, 5 в текст, одна перерисована), около 33 ₽. Лежат у НАС в public/img/blog — именно потому, что 26 старых обложек умерли во временном хранилище провайдера. Обложки старых 26 статей по решению владельца остаются типографскими. СМОТРЕТЬ И СОГЛАСОВАТЬ: /blog/proba — разводная страница, клик открывает статью в настоящем оформлении сайта. Публикация отдельным запуском после «да».
feature
Вид: починен мазок, знак в гармошке, обложки в карточках
СДЕЛАНО. МАЗОК: владелец дважды писал, что фраза «не наполнят» не видна. Первый раз я покрасил подложку в белый — причина была не в цвете. Подложка рисуется псевдоэлементом с z-index:-1 и уезжала ПОД фон блока, на экране оставался тёмный текст на тёмном. Дал мазку свой слой — теперь читается везде. Заодно отступ, чтобы подложка не наезжала на предыдущее слово. ГАРМОШКА: у раскрытого вопроса стоял плюс, у закрытых крестик — значок читался как «закрыть» там, где закрывать нечего. Перевёрнуто. ОБЛОЖКИ: карточки блога и шапка статьи показывают картинку, если она живая; мёртвые ссылки по-прежнему отсекаются. Ворота: 58 из 58. Проверку корня обновил — она требовала переадресации на вход, а корень теперь показывает витрину.
feature
Логотип: один знак на весь сервис
СДЕЛАНО. Владелец: «шапки везде разные, логотипа где-то вообще нет, где-то просто цифра 24». Так и было: витрина рисовала знак словами, кабинет и студия лаймовым квадратом с цифрой, админка тоже цифрой, вход цифрой и словом врозь. Логотип у бренда при этом есть и лежит на рабочем сервисе. ВЗЯЛ ИСХОДНИКИ С ПРОДА и собрал два лок-апа: строкой для шапок (в строке высотой 26 пикселей слово в две строки нечитаемо) и блоком для подвала, входа и картинки ссылки. Плюс вариант с тёмными чернилами: в светлой теме кабинета от логотипа оставались только лаймовые буквы, «24 контент» пропадал. Теперь знак живёт в одной части `знак.tsx` и правится в одном месте. ЗНАЧОК САЙТА тоже из фирменного знака, а не набранная шрифтом цифра: ico 16/32/48, иконка телефона 180, 192 и 512. Картинка ссылки перерисована с логотипом.
feature
Шапка закреплена и работает на телефоне
СДЕЛАНО. Шапка была на месте и уезжала со страницей: на длинной статье человек оставался без навигации. Теперь закреплена сверху, с подложкой и размытием. НА ТЕЛЕФОНЕ РАНЬШЕ МЕНЮ ПРОСТО ПРЯТАЛОСЬ правилом display:none, и попасть на страницы можно было только через подвал. Сделал кнопку и выпадающий список из того же набора разделов. ПОЙМАЛ ПРИ ПРОВЕРКЕ: правило не срабатывало, потому что старый селектор с элементом (.shapka nav) весит больше, чем селектор по классу. На телефоне меню оставалось строкой и уезжало за край экрана. Видно только глазами, кодом не поймать. Якорям добавлен отступ: переход на /lp#chto ставил заголовок раздела ровно под шапку.
feature
Статьи: убрано то, что выдаёт машинный текст
СДЕЛАНО. Владелец: «убери палево типа количества знаков, длинные тире и прочее». Со страницы согласования убран счётчик знаков. Все пять статей переписаны целиком: было 151 длинное тире на пять текстов, стало ноль. Заодно убраны машинные приёмы: одинаковые зачины разделов «Что происходит / Что делать» в каждом пункте, слово «Итого», ровные тройки перечислений. Объём сохранён: от 6,7 до 9,5 тысяч знаков. Тире убрано и из текстов витрины, которые писал я. В перенесённых с рабочего сервиса (страницы под нишу, журнал обновлений, правовые документы) не трогал ничего: это чужой готовый текст. ЦЕНЫ ПОПРАВЛЕНЫ по словам владельца: Veo 3.1 Лайт 50 ₽, Veo 3.1 Фаст 60 ₽.
feature
Почта info@24contentzavod.ru везде
СДЕЛАНО. В правовых документах, снятых с рабочего сервиса, стояла почта другого домена (Info@myvision.su) в десяти местах. Заменена. Правка внесена и в снимающий скрипт, иначе следующее снятие вернуло бы старый адрес обратно. Почта добавлена в подвал отдельной строкой вместе с годом и Телеграмом: бухгалтерия клиента ищет реквизиты именно там, и если их нет, следующий вопрос приходит в поддержку.
feature
Приёмка глазами: что нашлось и починено
ПРОВЕРЕНО по всем страницам витрины и кабинета, на широком экране и на телефоне. НАЙДЕНО И ПОЧИНЕНО: (1) меню на телефоне уезжало за экран, см. задачу про шапку; (2) в светлой теме кабинета логотип наполовину пропадал; (3) на витрине показывались оба варианта знака сразу — общее правило для картинок весило больше; (4) в студии в «последних работах» висела сломанная картинка: карточка рисовала <img> на любой файл, а у проверки на вирусность результат это отчёт report.json — теперь плитка с подписью; (5) плашка «черновик» пряталась под закреплённой шапкой, переехала вниз экрана; (6) страницы ниш и статьи блога потеряли картинку ссылки: свой блок openGraph перебивает корневой целиком, картинку пришлось указать явно. Битых ссылок нет: обошёл 68 адресов со всех страниц витрины, все отвечают. Ворота 58 из 58.
feature
Блог: пять статей согласованы и опубликованы
СДЕЛАНО. Владелец посмотрел на /blog/proba и сказал «статьи ок». Опубликованы все пять: цена ведения соцсетей, выбор модели для видео, почему не досматривают, контент-план, ИИ-аватар. Стоят первыми в блоге, с обложками. Разделы пересчитались: SMM 13, Видеоконтент 11, AI и автоматизация 4, Кейсы 3. В карте сайта стало 46 адресов вместо 41, все пять попали и в карту, и в выжимку для ИИ-поиска. У статьи с обложкой картинка ссылки теперь её собственная, а не общая заставка.
feature
Корень: главная открывается и тому, кто вошёл
ПОЧИНЕНО. Владелец: «не могу перейти на главную, кидает в лк сразу». Корень уводил вошедшего в его бренд, и получалось, что из кабинета нельзя посмотреть собственный сайт: клик по логотипу или по пункту «Главная» возвращал обратно. Теперь витрина показывается всем. Вошедшего узнаём в оболочке витрины и вместо кнопок «Войти» и «Начать» показываем «В кабинет»: один бренд ведёт прямо в него, несколько на разводную. Ошибку при опросе базы глушим намеренно, иначе сайт падал бы целиком из-за кнопки в углу. В смоук добавлена проверка: вошедший получает с корня 200 и видит «В кабинет». Раньше такой проверки не было вовсе, поэтому поломку нашёл владелец, а не ворота. Ворота: 58 из 58.
feature
Обход всех страниц: инструмент и находки
СДЕЛАНО. Написан tools/obhod.ts: заводит свою организацию с данными, проходит 68 адресов (витрина, кабинет, студия, админка) и смотрит три вещи — код ответа и время, следы поломки в вёрстке (Application error, NaN ₽, Invalid Date), и экраны без единой ссылки и кнопки. Последнее важно для новичка: технически исправная страница, с которой некуда идти, это тупик. Прогон чистый: 500-х нет, самая медленная страница 300 мс.
feature
ПОЛОМКА: вход по приглашению не работал при вставке ссылки целиком
ПОЧИНЕНО. Под полем написано «можно вставить весь адрес, лишнее уберём сами». Разбор делал split(«/»).pop() и потом split(«?»)[0], то есть от ссылки /login/invite?token=ABC оставалось слово «invite». Вход не проходил ровно у того, кто делал как написано; работал только случай с голым ключом. Разбор вынесен в функцию ключИзСсылки и покрыт проверкой на девять видов вставки (полный адрес, ключ в пути, голый ключ, хвост с меткой рассылки). Плюс ключ из адреса теперь подставляется сам: человек пришёл по ссылке, и просить вставить её снова незачем.
feature
ПОЛОМКА: посты с фото товара не собирались вообще
ПОЧИНЕНО. Выпуск из плана вставал на шаге «картинка» и молча повторял попытку раз в пятнадцать минут. Причина: линии поста, карусели и аватара кладут в образцы путь в нашем хранилище (фото товара, логотип), а провайдер скачивает картинку сам и путь открыть не может. Стояла проверка, которая называла это ошибкой вызывающего. Исправлено в одном месте: подпись пути теперь делает сам модуль рисования, а не каждая линия по-своему. Проверено живьём — выпуск собрался за 162 секунды, картинка с русским текстом и логотипом на месте. Тест kie.test.ts переписан под новое поведение.
feature
ПОЛОМКА: три кнопки плана отвечали «NEXT_REDIRECT»
ПОЧИНЕНО. «Расширить план», «Добавить контент» и «Придумать идеи» показывали ошибку, хотя работа делалась: redirect() в Next бросает своё исключение, а он стоял внутри try и попадал в catch. Переадресация вынесена наружу во всех трёх. Чтобы не повторилось, проверка tools/server-actions.check.ts теперь ловит и этот случай наравне с замкнутой функцией: обе ошибки одинаково коварны — страница открывается, типы довольны, ломается только отправка формы.
feature
Надёжность: после перезапуска воркера работы больше не ждут полчаса
СДЕЛАНО. Заказ, застигнутый перезапуском воркера, оставался заблокированным в очереди: замки снимались только у задач старше тридцати минут. При каждом выкате это стоило клиенту получаса ожидания с удержанными деньгами. Теперь при СТАРТЕ порог минута (новый процесс ещё не взял ни одной задачи, значит любой замок чужой и мёртвый), а по кругу работает прежний порог. Плюс каждая неудачная попытка шага теперь пишет в лог причину и решение лестницы: именно из-за молчания поломку с образцами искали двадцать минут вместо одной.
feature
Студия: холст и пульт вместо анкеты
СДЕЛАНО по образцу, который показал владелец. Наверху холст: пока идёт сборка — шаги и спокойная волна, готово — картинка или ролик крупно, с кнопками «Скачать» и «Открыть работу». Внизу по центру пульт: виды работ, поле описания, настройки (кадр, разрешение, длительность, версия, основа) выпадают вверх, справа кнопка с ценой. Помощники «Улучшить описание» и «В банк идей» переехали в пульт: раньше это были отдельные карточки посреди страницы, и человек не связывал их с полем ввода. Заодно починена кириллица в адресе студии: переадресация после отправки уезжает заголовком ответа, и русское «/studio/картинка» роняло страницу с ERR_INVALID_CHAR.
feature
Интерфейс глазами новичка: девять правок
СДЕЛАНО. (1) Лента работ показывала два чёрных прямоугольника в пол-экрана: ролики грузились, но браузер не рисует кадр до воспроизведения. Просим кадр на 0.1 секунде, плитки уменьшены. (2) «Готово за неделю: 0, −2 к прошлой неделе» читалось как «минус две работы» — теперь «неделей раньше было 2». (3) Раздел «Входящие» объяснял клиенту про перенос на новое ядро и «настоящий объём работы» — наша кухня на клиентском экране. (4) Календарь назывался «План», ровно как соседний пункт меню. (5) Завод без расписания молча ничего не выпускал: теперь об этом сказано и на карточке, и на самом заводе, с кнопкой. (6) «Файл не открылся» на готовой проверке вирусности: это не поломка, а разбор вместо картинки. (7) В списке людей первым шёл «без почты · без почты». (8) Заголовком работы стояло задание сценаристу на три строки вместо короткого названия. (9) Кнопки тарифов вели вошедшего клиента на регистрацию.
feature
Формы: свои флажки и списки вместо системных
СДЕЛАНО. Системные квадратики Windows посреди тёмного кабинета выглядели как чужая вставка. Покрашены флажки, переключатели, выпадающие списки и кнопка выбора файла — в общем оформлении, значит сразу во всех формах сервиса. Механику не трогали: это по-прежнему обычные input и select, работают с клавиатурой и без скриптов. Плюс подсказка «где взять ключ» на подключении соцсети: раньше экран просил ключ доступа и не говорил, где его берут, — для человека без опыта с ботами это тупик.
feature
Телефон: горизонтальная прокрутка убрана
СДЕЛАНО. На ширине 390 px дашборд и студия разъезжались вбок: длинная тема работы растягивала карточку продукта, а строка истории студии не переносилась. Проверены восемнадцать экранов кабинета и студии — прокрутки вбок не осталось нигде. Баланс в шапке перестал ломаться на две строки.
feature
ПОЛОМКА: холст студии не показывал готовую картинку
ПОЧИНЕНО. Библиотека работ отрезала список ДО фильтра по виду: SQL брал последнюю работу любого вида, а «только картинки» применялось уже в коде, поверх этой одной строки. Если последней оказалась озвучка или разбор, ответ приходил пустой. Холст просил одну последнюю картинку и получал пустоту при десяти готовых картинках. Тем же страдал выбор основы для ролика: список молча терял картинки. Теперь при заданном виде берём из базы с запасом и обрезаем после фильтра.
feature
Светлая тема действовала только в кабинете
ПОЧИНЕНО. Человек включал светлую тему, кабинет светлел, а студия, админка и вход оставались чёрными: тему применял сам переключатель, а живёт он в боковом меню кабинета. Нет меню — некому поставить признак. Теперь выбор применяется на уровне всего документа и до первой отрисовки, иначе светлая страница успевает моргнуть чёрным. Заодно студия и админка переведены с жёстких цветов (#0a0a0f, #8b93a7) на общие переменные темы: без этого они не посветлели бы всё равно.
feature
Обходчик страниц доведён до чистого прогона
СДЕЛАНО. tools/obhod.ts проходит 73 адреса и заканчивается «С бедой: 0». Убраны ложные срабатывания: виды студии названы по-русски и адрес надо кодировать, служебная страница здоровья живёт без ссылок по назначению, а «страницы нет» в ответ на админку — правильное поведение, существование служебного раздела чужому не подтверждаем. Инструментом можно пользоваться после каждого крупного изменения: он показывает коды, время, следы поломки в вёрстке и экраны, с которых некуда идти.
feature
План: к идее привязывается фото товара
СДЕЛАНО по образцу рабочего сервиса: под темой идеи стоит строка «фото не привязано · выбрать», выбор идёт из товаров бренда с фотографиями, без привязки сервис решает сам. Поле в базе было заведено миграцией 0062, теперь появился экран. Номер товара из формы не принимаем на веру: сервер ищет его среди товаров бренда. Проверено сквозняком на живых деньгах: привязал «Диван-кровать Осло», запустил выпуск, работа собралась за 173 секунды — на картинке этот диван, с русским текстом и логотипом бренда.
feature
Несколько картинок за раз: студия, СММ-Центр, линии
СДЕЛАНО по задаче владельца: «кидаю 2-3 картинки и говорю — первая человек, со второй возьми одежду, с третьей улицу». НАЙДЕНО: вызов провайдера это умел (поле принимает список), но по всей цепочке был протянут ровно один образец. Студия хранила исходник строкой, линия оборачивала его в массив из одного. СДЕЛАНО: заказ и задание держат список, порядок сохраняется — человек описывает картинки номерами, и перестановка ломает задание. В пульте студии появилась кнопка «+» для своих файлов (до восьми) и множественный выбор из готовых работ с номерами на плитках; под полем ввода строка «1. … 2. … 3. …», чтобы было видно, на что ссылаться. В СММ-Центре то же самое для поста и карусели: свои картинки уходят тем же отправлением и встают первыми, перед образцом стиля и фото товара, чтобы не сдвигать нумерацию. Предел образцов теперь свой у каждой модели: gpt-image-2 держит 14, нанобанана 8. Раньше резали по 16 для всех, лишние модель отбрасывает молча. ПРОВЕРЕНО ЖИВЬЁМ: три картинки из библиотеки + описание «диван с первой, комнату со второй, растение с третьей» — провайдеру ушли все три (в записи запроса «образцов: 3»), кадр собран из трёх источников. Первый прогон показал 1 образец: воркер работал по старому коду, после перезапуска стало 3.
feature
Сценарий у идеи: пишет сервис, правит человек
СДЕЛАНО по развилке владельца. Ручная работа и работа с ИИ уже исходили из того, что написал человек. Третий случай — автоматика завода — не работал: сценарий рождался внутри работы, человек видел его только в готовом ролике, и «докрутить сценарий» существовало на словах. Миграция 0063: у идеи появились сценарий и отметка, кто его писал. В плане: кнопка «Написать сценарий сервисом» (по теме, крючку и призыву, меньше рубля) и поле для правки руками. При запуске выпуска сценарий уезжает в задание вместе с крючком, призывом и привязанным фото, а линия видео берёт готовое вместо того, чтобы писать своё поверх согласованного. Пустой сценарий по-прежнему значит «придумай сам». Две правки по ходу: модель возвращала JSON внутри строки (человек видел фигурные скобки вместо сценария) и обрывала ответ на полуслове из-за лимита токенов. Первое разворачиваем в текст, второе вылечено лимитом и просьбой уложиться в тысячу знаков. ПРОВЕРЕНО ЖИВЬЁМ: сценарий написан («В кадре: … Текст: …»), доехал до задания работы, работа собралась.
feature
Veo 3.1 Быстрый: цена в ядре уже 60 ₽
ПРОВЕРЕНО, менять не пришлось. В реестре ядра стоит 60 ₽ при себестоимости около 28 ₽ за ролик (0.30 доллара за восемь секунд 720p). Владелец назвал те же 60 ₽. Расхождение остаётся с каталогом рабочего сервиса: там та же модель стоит 90 ₽. Боевую таблицу не трогал — это цена для живых клиентов прода, и менять её надо отдельным решением, а не по ходу работы над ядром.
feature
готовS20 · С14 · Заводы: настройки по полкам3/3

Цель: Разложить настройки конвейера по страницам, страницу завода сделать пультом

Настройки завода: шесть отдельных страниц вместо двух длинных
СДЕЛАНО. Было: все настройки конвейера на двух страницах, «о чём завод» шестью карточками подряд, плюс форма расписания посреди страницы завода. Половина настроек экрана не имела вовсе. Стало: раздел settings с шестью страницами и своим меню — расписание и форматы, о чём завод, голос и ограничения, рубрики, как выглядит, соцсети. ГЛАВНОЕ ПРИ РАЗРЕЗАНИИ: добавил частичное сохранение позиционирования. Прежняя функция писала документ целиком, и страница «Голос» сохранила бы тон, обнулив тему, аудиторию и рубрики: их полей в её форме нет. Ошибка тихая, вылезла бы через неделю странными работами. Старые адреса /about и /video ведут на новые места, а не в «не найдено».
feature
Страница завода стала пультом
СДЕЛАНО. Владелец: «сейчас контент-заводы выглядят нерабочими». Причина была в том, что на одной странице лежали и состояние, и настройки: форма расписания занимала два экрана, а режим и план терялись между ними. Теперь на пульте только состояние и действия: сводка «что выпускает» (форматы, дни, сколько в день, товары) с кнопкой «Изменить», режим со сменой под причину, плашка «Настройки завода» с шестью разделами, план с утверждением и лента произведённого.
feature
Соцсети завода: экран к механике, которая уже работала
СДЕЛАНО. Владелец спросил, как трём заводам публиковать в три разных инстаграма. Ответ: механика в ядре была с самого начала (аккаунтыЗавода), уникальность в базе стоит по тройке бренд+сеть+аккаунт, то есть несколько аккаунтов одной сети на бренде допустимы. Не было только экрана. Сделал: страница «Соцсети» у завода со списком подключённых аккаунтов бренда и галочками. Ничего не отмечено — уходит во все подключённые. Отвалившийся аккаунт помечен прямо в строке.
feature
готовS21 · С15 · Контент-план как продукт6/6

Цель: Темы придумывает сервис, состояния видны, выпуск можно запустить кнопкой

Темы для плана придумывает сервис
СДЕЛАНО. На экране плана стояло двенадцать строк с рубриками и подписью «Задайте тему: планировщик раскладывает выпуски по дням, но не придумывает, о чём они». Запустить из такого плана было нечего. Появился модуль plan-ideas: берёт профиль завода той же функцией, что и производство (бренд плюс переопределения конвейера), показывает модели рубрику каждого пункта и уже занятые темы, заполняет до двенадцати пунктов за раз. Модель дешёвая, запрос стоит меньше рубля. ЛОВУШКА, КОТОРУЮ ПОЙМАЛ: первая версия искала пункты по заголовку «Тема не задана», а планировщик кладёт в заголовок название рубрики. Кнопка не появилась бы никогда. Признак пустого пункта теперь по идее, а не по заголовку. Проверка tools/plan-ideas.check.ts: модель подменена заглушкой, гейт не платит за прогоны. 11 проверок, включая «пункт с работой не переписан» и «пропущенный не переписан».
feature
Кнопка «Сделать сейчас» для пункта плана
СДЕЛАНО. До неё режим «только по кнопке» означал «никак»: план есть, деньги есть, а произвести выпуск из плана было нельзя — автозапуск в ручном режиме молчит по определению, и другого пути не существовало. Функция запуститьПункт: день не проверяет (пора решил человек), режим завода не смотрит (нажатие и есть разрешение), но утверждение плана требует — смета согласуется до трат. Пропущенный пункт не запускает. Если заказ не встал (не хватило денег), пункт освобождается, иначе он выглядел бы сделанным. На кнопке написана цена.
feature
План читается как календарь
СДЕЛАНО. Было: список форм, у каждого выпуска поле темы, поле даты и две кнопки. Понять по нему, что происходит с лентой, было нельзя. Стало: пункты сгруппированы по дням («24 августа, пн»), у плана три состояния — Черновик, Утверждён, В работе, у пункта видно формат, цену, состояние работы и метку «Без темы». Сверху счётчик: сколько выпусков, на какую сумму, сколько без темы, сколько уже в работе.
feature
Контент-план пересобран по рабочему сервису, а не заново
ПЕРЕДЕЛАНО ПОСЛЕ ЗАМЕЧАНИЯ. Владелец: «ты изобретаешь многое с нуля, убирая половину функционала, который мы выстрадали, и делаешь сложный юай». Он прав: первая версия свелась к списку полей. Взял прод как эталон и перенёс модель: у идеи своё состояние (предложена, одобрена, отклонена), цель, крючок, призыв, визуальный стиль, важность, фото. Миграция 0062. Перенесены действия: Одобрить, Отклонить, Переделать идею, Редактировать, Пропустить, Удалить, Сделать выпуск. Плюс вкладки Текущий план и Прошлые дни, фильтр по форматам со счётчиками, группировка по дням с пометками Сегодня и Завтра, счётчики Идей/Одобрено/Готово/Смета. Модель теперь придумывает не только тему, но и крючок с призывом и целью: просить их отдельно значит платить дважды за один контекст.
feature
Свои идеи и расширение плана
СДЕЛАНО. «Своя идея»: человек ставит в план своё на конкретный день, с форматом, темой, крючком и призывом. Сразу считается одобренной: спрашивать согласия со своей же мыслью незачем. «Расширить план» повторяет прод и делится надвое: «Добавить дни» продлевает план по расписанию завода, «Добавить контент» уплотняет уже расписанные дни (было три выпуска в день, стало пять). Оба не работают на утверждённом плане: смета согласована, дописывать в неё значит менять сумму после согласия.
feature
Контент-план показывает результат: миниатюра и окно работы
СДЕЛАНО по замечанию владельца: «нельзя посмотреть картинку, контент, подробности; в проде открывается модалка, где можно сразу опубликовать, переделать». Он прав: у идеи с готовой работой карточка показывала слово «Готово» и ссылку, и чтобы увидеть картинку, надо было уйти на другую страницу и вернуться. ПОЯВИЛОСЬ: миниатюра результата прямо в карточке и кнопка «Посмотреть». В окне — картинка или ролик крупно, текст поста целиком, сценарий, состояние и действия: Согласовать, На доработку с замечанием, Скачать, Открыть целиком. Окно нативное (dialog): закрывается по Esc и по клику мимо, без своей библиотеки. Данные собираются одним запросом на всю страницу: файлы, тексты, согласование. По запросу на идею это два десятка походов в базу при открытии плана. ПРОВЕРЕНО ЖИВЬЁМ: окно открывается у готовой и у провалившейся работы, «Согласовать» из окна отработало. У провалившейся вместо выдуманной кнопки повтора написано, что деньги вернулись: механики повтора одной работы в ядре нет, и рисовать кнопку под неё нельзя.
feature
планS22 · Проверки по сроку0/1

Цель: Отложенные проверки: сработало ли то, что чинили

Воркер ядра: не вернулся ли крах-цикл после починки адреса базы
01.09 · завтра
Смотрим, не вернулся ли бесшумный цикл перезапусков после починки 25.08 (адрес базы заменён на имя контейнера, воркер ждёт базу, /etc/hosts синхронизируется кроном). КОМАНДЫ ПРОВЕРКИ: ssh cfru "pm2 list | grep -E 'cz-core|cz-worker'" — счётчик ↺ должен быть маленьким и не расти между двумя запусками, uptime — днями. ssh cfru "curl -s http://127.0.0.1:3160/api/health" — ожидаем {"живой":true,"база":true}. ssh cfru "getent hosts supabase-db" — имя должно резолвиться в текущий адрес контейнера; сверить с docker inspect supabase-db. ssh cfru "grep -c . /root/.pm2/logs/cz-worker-error.log" — лог не должен снова расти на десятки МБ. Если счётчик опять растёт — смотреть первую строку ошибки: адрес это или что-то новое. Разбор аварии: docs/ИНЦИДЕНТЫ.md, запись от 25.08.2026.
check

Ворклог (места остановки)

30.08.2026, 10:59:02 · · claude

КЛЮЧИ ВЗЯТЫ СО СТЕЙДЖА, А НЕ ВЫПРОШЕНЫ. Владелец: «почему просишь токены и ключи, которые уже есть все на стейдже или в проде». За сессию я дважды попросил лежащее рядом; токен телеграм-бота вообще стоял в .env самого ядра как CZ_ALERT_BOT_TOKEN. Сверил списки переменных ядра, стейджа и прода, добрал недостающее: бот Завода для поддержки, OPENAI_API_KEY, VK, Яндекс, Apify. Правило записано в память. ПОДДЕРЖКА В ТЕЛЕГРАМЕ ВКЛЮЧЕНА. Вебхук поставлен клиентскому боту @Contetn_zavod_MyVision_pub_bot, оповещения переведены на уже настроенный канал тревог. Проверено, что стейдж этого бота не слушает — публикация им не сломалась. ЛОЖНЫЕ ОПОВЕЩЕНИЯ УБРАНЫ. Владелец увидел в Телеграме три обращения и спросил, настоящие ли. Все три были мои: два от воротных проверок, одно от теста вебхука. Оповещение стояло внутри доменной функции — перенёс в слой входа, добавил молчание под проверками и CZ_CHECK=1 в воротах. Следующий выкат прошёл тихо. РАНЬШЕ В ЭТОТ ЖЕ ДЕНЬ: счета юрлицам через Точку доведены до настоящих (клиент банка был перенесён, но его никто не звал; TLS чинился корневыми Минцифры; ИНН теперь проверяется до банка), нарезка рилсов убрана по решению владельца.

ПОРЯДОК ВЛАДЕЛЬЦА: сначала отсутствующий функционал, потом наполовину сделанное, потом решения про адреса и ядро. В РАБОТЕ: публичный API — ключи, квоты, вебхуки наружу, документация из кода. Дальше: Лия (её нет вовсе), режиссёр роликов (11 задач в С12). ОДНО ДЕЙСТВИЕ ЗА ВЛАДЕЛЬЦЕМ: написать @Contetn_zavod_MyVision_pub_bot любое сообщение — обращение должно появиться в админке, ответ оттуда прийти обратно в Телеграм. Сам я это звено проверить не могу: бот не пишет человеку первым, пока тот ему не написал. ЖДЁТ ОТМАШКИ: смешанный прогон нагрузки, 10 постов и 2 ролика Veo Lite одновременно, около 190 руб.

30.08.2026, 05:52:45 · · claude

СЧЕТА ЮРЛИЦАМ ТЕПЕРЬ НАСТОЯЩИЕ. Владелец поправил: оплата Точки подключена и ключ есть — он говорил это дважды, а я подавал обратно как «нужны ключи». Ключи лежали на стейдже content-factory, перенесены в ядро. Хуже другое: клиент банка был перенесён побайтово ещё раньше, но его НИКТО НЕ ЗВАЛ — кнопка «Счёт для юрлица» писала строчку в базу и обещала документ, которого не существовало. Связал: счёт создаётся в Точке, уходит на почту письмом от банка с PDF, номер хранится в payments.external_id, закрытие в админке удаляет счёт и в банке. Две ловушки по дороге. TLS к банку рвался с SELF_SIGNED_CERT_IN_CHAIN: сертификат Точки выдан Russian Trusted Sub CA (Минцифры), которого нет в наборе Node — положил корневой и подчинённый в certs/, прописал NODE_EXTRA_CA_CERTS; склеивать PEM надо с переводом строки, иначе openssl говорит bad end line, а Node молча не доверяет. И ИНН: банк на невалидном отвечает 424, а человек к тому моменту уже прочитал «счёт выставлен» — теперь контрольная сумма проверяется до обращения в банк. ПРОВЕРЕНО НА ЖИВОМ БАНКЕ дважды: инструментом (create → payment_waiting → delete) и через кабинет (выставил счёт на 4990 → external_id записан → закрыл из админки → банк отвечает «Документ не существует»). НАРЕЗКА РИЛСОВ УБРАНА по решению владельца: он перестал оплачивать сторонний сервис. Кода в ядре не было, оставалось обещание в ленте обновлений — человек читал и шёл искать. Задача в дашборде отменена, факт записан в память. ПОДДЕРЖКА ОДНИМ ПОТОКОМ — первое из отсутствующего функционала. Обращение из Телеграма или МАКСа заводит нитку в той же очереди, что и кабинетное, ответ уходит обратно в мессенджер, на первое сообщение человек получает подтверждение. По дороге найдена тихая поломка: очередь соединялась с людьми внутренним JOIN, и обращение от незарегистрированного человека было бы принято, записано и невидимо. Семь проверок в vitest. Механика спит, пока не задан токен бота поддержки.

ПОРЯДОК ЗАДАН ВЛАДЕЛЬЦЕМ: сначала функционал, которого нет, потом наполовину сделанное, потом решения про адреса и ядро. ОТСУТСТВУЕТ, В РАБОТЕ: публичный API — ключи, квоты, вебхуки наружу, документация из кода. Дальше по списку: Лия (её нет вовсе), режиссёр роликов (11 задач в С12). НУЖНО ОТ ВЛАДЕЛЬЦА для включения поддержки: SUPPORT_TELEGRAM_TOKEN (новый бот или разрешение на публикационный Contetn_zavod_MyVision_pub_bot), SUPPORT_ALERT_CHAT, SUPPORT_HOOK_SECRET. ЖДЁТ ОТМАШКИ: смешанный прогон нагрузки — 10 постов и 2 ролика Veo Lite одновременно, около 190 руб. Раньше нагрузка была раздельной, вопрос «не встают ли короткие работы за длинными» не проверен. ПРОВЕРЕНО ПОПУТНО, что уже сделано вопреки записям в дашборде: мастер создания заводов есть, с помощником по брифу; проверка на вирусность есть; поддержка в кабинете работала.

29.08.2026, 11:04:31 · · claude

СЧЕТА ЮРЛИЦАМ ЗАКРЫТЫ СКВОЗНЯКОМ. Раздел «Счета» в админке: кто ждёт, реквизиты, сколько дней висит; кнопки «Оплата пришла — включить тариф» и «Закрыть без оплаты». Проверено целиком: выставил счёт из кабинета, увидел с реквизитами, включил — подписка активна на месяц, платёж оплачен, деньги на счёте клиента. Следы убраны корректирующей проводкой: править проводки кошелька база запрещает сама, и это правильно. Текст для клиента переписан по решению владельца: оплатили — пишите в поддержку с платёжкой, включим сразу. ВИТРИНА РАБОТ: прокрутка вбок убрана окончательно. Причина была в моём же старом правиле ниже по файлу оформления (grid-auto-flow: column) — оно перебивало новое и заодно делало плитки разной ширины. Осталось одно правило: 15 работ в три ряда, все одного размера. КАТАЛОГ МОДЕЛЕЙ KIE ВЫТАЩЕН. 447 записей, 160 живых имён: 49 картиночных моделей, 65 видео, 20 музыкальных. Реестр расширен с 12 до 34 — заведены дешёвая картинка (z-image 0.4 руб себестоимости), flux-2 pro, seedream 5 lite, ideogram v3, imagen4, qwen3, дешёвые ролики (seedance-2-mini, grok-imagine), kling 3.0, minimax H3, липсинк (infinitalk 1.4 руб/с и kling avatar 3.8 — против 12.8 у нашего omnihuman), апскейлы. Каждый ключ сверен с каталогом на выкате: выдуманных имён нет. В студии появился выбор движка с ценой у каждого — переключается ссылкой, чтобы цену считал сервер тем же кодом, что и списание. VEO LITE ЗАВЕДЁН — по замечанию владельца. Я гонял Fast (28.5 руб), потому что Lite в реестре не было. Теперь проба идёт на Lite: 30 руб клиенту при себестоимости 14.25, проверено живьём. Fast остался вторым движком за 60. Отдельно признана моя ошибка: Lite работает с картинками-образцами, разница только в качестве. СУБТИТРЫ К ВИДЕО ПЕРЕНЕСЕНЫ. Владелец спросил, куда они делись — ответ: их не переносили. В ядре субтитры были только у ролса, из нашей же озвучки; ролик, говорящий чужим голосом, оставался без подписей. Перенесено скриптом и сверяется воротами: раскладка ASS, караоке по словам, цвета, тайминги. Распознавание заменено на ElevenLabs Scribe — ключа OpenAI в ядре нет и регион закрыт, а Scribe даёт тайминги по словам. Проверено на ролике Veo: распознал произнесённую фразу дословно и вшил с подсветкой. Настройки берутся с завода, те же, что у ролса. Девять проверок на разбивку речи по строкам. Две идеи владельца про режиссуру занесены в С12 как задачи на будущее, в работу не взяты.

ГЛАВНОЕ, ЧЕГО НЕТ: перенос данных с прода (организации, бренды, люди, готовые работы и файлы, балансы, банки качества, сверка) — без него ядро остаётся отдельным сервисом с пустой базой. ЖДЁТ ОТМАШКИ: смешанный прогон нагрузки — 10 постов и 2 ролика Veo Lite одновременно, около 190 руб. Раньше нагрузка была раздельной: посты залпом, ролики отдельно, и вопрос «не встают ли короткие работы за длинными» не проверен. У воркера три места. ХВОСТЫ ПО СДЕЛАННОМУ: выбор движка есть у 3 инструментов из 13 — дешёвый липсинк и апскейлы заведены в реестр, но человеку не видны; субтитры работают в линии видео, у аватара и студийных роликов их нет; свои картинки в карусели едут только на обложку. РЕШЕНИЯ ЗА ВЛАДЕЛЬЦЕМ: адреса на nginx, ключи Точки, потерянные файлы работ, сравнение Veo Lite против wan 2.7. ГИГИЕНА: около тридцати задач в закрытых спринтах висят без статуса (линии поста, аватара, ролса, кошелёк, очередь, права) — функционал работает, статус не проставлен. Предложено выверить разом.

28.08.2026, 06:25:17 · · claude

Каталог Kie вытащен (447 записей), реестр расширен с 12 до 41 модели, в студии появился выбор движка с честной ценой. Нагрузка: десять постов разом — 10 из 10 готовы, ноль провалов, худший заказ 224 секунды. Ролс не работал ни разу и потребовал четырёх починок: пустая раскадровка, озвучка мимо прокси (ElevenLabs отвечает нам 403), логотип путём вместо ссылки, кадры-картинки превращались в mp4. Отдельно поймано худшее: работа помечалась «Готово» без главного файла. Теперь ролс собирается целиком, с субтитрами, проверено глазами. Расход 606 руб против согласованных 215 — перерасход на отладочных прогонах, честно записан отдельной задачей.

Живой прогон формата «видео» и аватара (владелец просил оставить ролики на конец). Там же проверить, не мешает ли кадрам приведение к видеоканону — в линии видео оно оставлено намеренно, но после истории с ролсом это надо посмотреть. Дальше: мастера создания заводов, вторая волна нагрузки (карусели), логотип в ролсе почти не виден.

27.08.2026, 16:57:48 · · claude

Мульти-референсы доведены до конца и проверены на живых деньгах: фото своего дивана + фото набережной дали пост с этим диваном на этой набережной и заголовком слово в слово. По дороге найдены и починены четыре поломки: обрыв ответа модели по лимиту токенов (жёг деньги и убивал работу), цена шага ниже себестоимости (запрещала повтор с самого начала), потеря принесённых файлов из-за сброса поля, и главное — агенты сочиняли своё вместо прямого указания клиента. Сделан вход под клиентом с плашкой и выходом, раздел админки «Производство» (выход, поломки, себестоимость шагов), обход 86 страниц встроен в ворота после сборки и теперь роняет выкат. На живые тесты ушло около 60 руб из 200, выданных владельцем; всего за сессию около 142.

Проверить те же принесённые картинки в карусели и ролике живьём (карусель 89 руб, ролик 272 — спросить владельца про трату). Дальше по его списку готовности к проду: очереди под нагрузкой и отказоустойчивость, полный боевой прогон аватара и субтитров, мастера создания заводов. Отдельно: лендинг обещает 49 моделей, реестр ядра знает 18 — решить, дописывать реестр или править обещание.

27.08.2026, 14:58:18 · · claude

Оплата подписки доведена до рабочего состояния: карта через кассу с автоматическим включением тарифа и счёт для юрлица с реквизитами. Стена лендинга перестала повторяться, витрина работ переехала на свои файлы и получила резиновую сетку. Ворота 1235.

Счета юрлицам сейчас ведутся вручную: нужен раздел в админке «ждут оплаты» и решение по Точке (в бэклоге С11). Осталось по прод-готовности: очереди под нагрузкой, прогон производства (карусель, видео, аватар, субтитры), вход под клиентом в админке, мастера создания заводов.

27.08.2026, 14:05:26 · · claude

Подготовка к выходу на прод, первая волна по списку владельца. Регистрация проверена живьём и починена ссылка возврата в письме. Появился предел частоты действий с проверкой в воротах. Витрина переехала на свои файлы и получила живое видео из работ заводов. В админке появился раздел «Деньги» по всем направлениям, и заодно найдено, что себестоимость писала только студия — исправлено во всех линиях. Контент-план показывает результат и даёт с ним работать. Ворота 1233.

Осталось по списку прод-готовности: очереди и отказоустойчивость под нагрузкой; полный прогон производства (карусель, видео, аватар, субтитры) на живых деньгах; вход под клиентом и аналитика в админке; мастера создания заводов. Отдельно: несколько образцов для видео (в картинках уже до 16), массовые действия в плане, каталог студии (витрина обещает 49 моделей, ядро знает 18).

27.08.2026, 12:01:17 · · claude

Три задачи владельца. Несколько картинок за раз доведены до конца по всей цепочке и проверены живьём. Сценарий стал сущностью идеи: пишет сервис, правит человек, производство берёт согласованное. Цена Veo сверена: в ядре уже 60 ₽. Ворота 1224.

На живые проверки ушло 136 ₽ из 200. Не сделано и требует отдельного захода: несколько образцов для ВИДЕО. У Kie это умеет Veo через свой режим с образцами, у остальных моделей поле другое; нужен разбор их API и проба на живых деньгах. Сейчас видео берёт один первый кадр. Ещё в очереди: массовые действия с галочками в плане, мастера создания заводов, перенос Вируса и Лии.

27.08.2026, 04:49:30 · · claude

Вторая половина автономного прогона: холст студии не показывал готовые картинки (LIMIT стоял до фильтра по виду), светлая тема действовала только в кабинете, к идее плана добавлена привязка фото товара. Обходчик доведён до чистого прогона: 73 адреса, ноль бед. Ворота 1224.

Потрачено на живые проверки 82 ₽ из 200, остаток у владельца. Проверены сквозняком: студия, план с одобрением и выпуском, расширение плана, генерация идей моделью, привязка фото к идее. Не проверено: публикация в соцсеть (сети не подключены) и оплата подписки.

27.08.2026, 04:13:41 · · claude

Автономный прогон, пока владелец в отъезде: обход всех страниц глазами и правки. Написан обходчик на 68 адресов. Найдены и починены три настоящие поломки: вход по приглашению не работал при вставке ссылки целиком; посты с фото товара не собирались вообще (путь в хранилище уезжал провайдеру вместо ссылки); три кнопки плана отвечали NEXT_REDIRECT. Студия переделана на холст и пульт по образцу владельца. Девять правок интерфейса под новичка, свои флажки и списки вместо системных, убрана горизонтальная прокрутка на телефоне. Ворота 1224, зелёные. На живые проверки потрачено 62 ₽ из 200.

Живьём проверено: генерация картинки в студии, одобрение идеи и выпуск из плана (собрался за 162 с), расширение плана, генерация идей моделью. Не проверено: публикация в соцсеть (ни одна сеть не подключена) и оплата подписки. Осталось из бэклога: перенос Вируса и Лии, публичный API, режиссёр роликов (С12), привязка фото к идее плана, массовые действия с галочками в плане. Владельцу на решение: цена Veo 3.1 Быстрый (каталог 90 ₽ против 60 ₽ у вас) и надо ли делать отдельный шаг «сгенерировать сценарий» в плане.

25.08.2026, 05:06:00 · Внеплановое · авария базы · claude

Разобрал тревогу Лии про крах-цикл cz-worker (+261 перезапуск за интервал). Оказалось: 24.08 перезапустили стек Supabase, контейнер базы переехал с 172.19.0.9 на 172.19.0.10, освободившийся адрес занял supabase-imgproxy. В настройках адрес базы был вписан числом — воркер падал на старте, pm2 поднимал заново: 74 048 заходов за сутки, 200% процессора, по 21 МБ логов каждые 6 часов. Ядро всё это время отвечало 503 «база: false», то есть не работало сутки. Очередь была пуста, заказы не пострадали. Починил тремя правками: (1) в обоих .env адрес заменён на имя контейнера supabase-db, а соответствие имени и адреса держит свежим новый /usr/local/bin/docker-hosts-sync.sh — пишет управляемый блок в /etc/hosts каждую минуту и при старте сервера; (2) в src/worker.ts добавлено дождатьсяБазы() — воркер ждёт базу по 15 секунд и пишет об этом в лог вместо бесшумного цикла перезапусков; (3) окружение pm2 обновлено через --update-env и закреплено pm2 save — иначе правка файла настроек не действовала вовсе и перезагрузка вернула бы старый адрес. Проверено: /api/health отдаёт 200 и «база: true», воркер подключился к очереди, счётчик перезапусков стоит, крон отрабатывает ежеминутно. Разбор записан в docs/ИНЦИДЕНТЫ.md, две грабли и симптом — в docs/14-ЭКСПЛУАТАЦИЯ.md.

Остались хвосты: (1) старые логи воркера — около 80 МБ однотипных ошибок (15 МБ текущий + три ротации по 21 МБ), чистить или нет — решение владельца; (2) pm2 поднимает упавший процесс без нарастающей паузы: сейчас это не опасно, потому что воркер сам ждёт базу, но для других процессов защиты нет — настройка требует пересоздания процесса (pm2 delete), не делал без спроса; (3) тревога сработала верно в первый же час, но сутки её никто не прочитал, а «online» в pm2 всё это время говорил обратное — стоит подумать, как доводить громче. Отдельно замечено в логе: graphile-worker не нашёл /var/www/cz-core/crontab, встроенный планировщик отключён — не трогал, к аварии отношения не имеет.

22.08.2026, 05:59:20 · · claude

Контент-план пересобран по образцу рабочего сервиса после замечания владельца: идея стала сущностью со своим состоянием и структурой, действия над ней перенесены все, добавлены свои идеи и расширение плана двумя способами. Ворота 1224, зелёные.

Чего в ядре пока нет против прода: привязка фото к идее (поле в базе есть, экрана нет), массовые действия с галочками «Выбрать все», текст стратегии над планом, сворачивание дней. Отдельной кнопки «Сгенерировать скрипт» не будет: в ядре сценарий это первый шаг работы, отдельной цены у него нет. Живьём не проверено: генерация идей на живой модели и «Сделать выпуск» (нужно разрешение на трату). Дальше С16: панель генерации для Студии и СММ-Центра.

22.08.2026, 05:06:31 · · claude

Контент-план стал рабочим: сервис предлагает темы, пункты сгруппированы по дням и показывают состояние, любой утверждённый выпуск можно запустить кнопкой с ценой. Ворота 1224 проверки, зелёные.

Живьём не проверено (нужно разрешение владельца на трату): генерация тем на живой модели, меньше рубля, и «Сделать сейчас» на одном посте, 20 ₽. Дальше С16: панель генерации для Студии и СММ-Центра.

22.08.2026, 04:10:50 · · claude

Начали пересборку контент-заводов. Настройки конвейера разъехались с двух длинных страниц на шесть коротких со своим меню, страница завода стала пультом: состояние, план, лента. Появился экран выбора соцсетей для завода — механика была, экрана не было. Согласовано с владельцем: настройки общие у бренда, завод переопределяет; аккаунты подключаются у бренда, завод выбирает галочками. Ворота 58 из 58.

С15: контент-план как продукт — генерация идей ИИ, состояния позиций, одобрение целиком и поштучно, ручные «Сделать сейчас» и «Опубликовать», автоматика для режима «работает сам». С16: панель генерации по образцу прод-канваса для Студии и СММ-Центра — холст, композер с «+» и голосом, строка настроек (ИИ/ручной, история промтов, модель, формат, качество, пресет), референсы, живой прогресс. Открытым остаётся: настройки постов и каруселей отдельными страницами делать только вместе с их применением в линиях, иначе получится настройка-обманка.

22.08.2026, 03:31:34 · · claude

Статьи согласованы и опубликованы, блог вырос до 31 материала. Починен корень: главная больше не выкидывает вошедшего в кабинет, вместо этого в шапке появляется кнопка «В кабинет». На эту дыру не было проверки, добавил в смоук.

1) Расхождение в цене Veo 3.1 Быстрый: каталог 90 ₽, владелец и реестр ядра 60 ₽. Прод не трогал, решить до переключения. 2) Инструкции (/docs) остаются на рабочем сервисе: экраны сняты со старого кабинета. 3) Каталог студии: витрина показывает 49 моделей, реестр ядра знает 18.

21.08.2026, 17:04:23 · · claude

Довёл витрину и кабинет до одинакового вида. Логотип взят с рабочего сервиса и поставлен везде, где раньше была цифра «24» или слова: витрина, кабинет, студия, админка, вход, подвал, значок сайта, картинка ссылки. Сделан вариант под светлую тему. Шапка закреплена и получила рабочее меню на телефоне. Пять статей переписаны без длинных тире и машинных повторов, счётчик знаков со страницы согласования убран, цены Veo поправлены по словам владельца. Почта приведена к одной во всех документах. Приёмка глазами дала шесть настоящих поломок, все починены; из них две (меню на телефоне и логотип в светлой теме) кодом не ловились.

1) Согласовать пять статей на /blog/proba и опубликовать: node tools/novye-stati.mjs --опубликовать. 2) РАСХОЖДЕНИЕ В ЦЕНЕ: в каталоге студии (таблица рабочего сервиса) «Veo 3.1 Быстрый» стоит 90 ₽, владелец называет 60 ₽, и в реестре ядра стоит именно 60 ₽. Прод не трогал. Решить, какая цифра верная, и поправить каталог до переключения. 3) Инструкции (/docs) остаются на рабочем сервисе: их экраны сняты со старого кабинета. Либо переснять под новый кабинет, либо в день переключения поставить переадресацию. 4) Каталог: витрина показывает 49 моделей студии, реестр ядра знает 18.

21.08.2026, 15:12:44 · · claude

Витрина доведена до состояния сайта, а не набора страниц: шапка и подвал сквозные, значок сайта во всех размерах, картинка ссылки, манифест. Добавлены правовые страницы и контакты — без них после переключения три адреса из подвала отдавали бы 404. Поставлены 301-переадресации со старых адресов рабочего сервиса. ГЕО оказалось про поиск через ИИ: сделан llms.txt из живых данных, robots с явным разрешением для ответных машин, разметка организации и сервиса на всех страницах, у статей — дата правки, раздел и картинка. Перенесён счётчик Метрики (тот же номер, только витрина и вход, не кабинет). Написаны пять новых статей руками и нарисовано 11 картинок (~33 ₽); лежат черновиками на /blog/proba, ждут «да». Починен мазок в финальных блоках — причина была в порядке наложения, а не в цвете, как я решил в прошлый раз. Ворота 58 из 58.

1) Согласовать пять статей на /blog/proba и опубликовать: node tools/novye-stati.mjs --опубликовать. 2) Владельцу на решение: на странице контактов рабочего сервиса стоит почта Info@myvision.su, а в подвале info@24contentzavod.ru — какая живая? Поставил вторую. 3) Расхождение в цене: владелец говорил про Veo 3.1 fast за 60 ₽, в каталоге «Veo 3.1 Быстрый» стоит 90 ₽, «Veo 3.1 Лайт» — 50 ₽. Какая цифра верная? 4) На рабочем сервисе в разметке стоит выдуманный рейтинг 4.8 из 150 отзывов — в ядро НЕ переносил: за фальшивую разметку отзывов поисковики снимают сниппеты. 5) Осталось по витрине: инструкции (нужны новые экраны переделанного кабинета). 6) Каталог: витрина показывает 49 моделей студии, реестр ядра знает 18 — решить, догоняем ли ядро каталог до переключения.

21.08.2026, 05:56:06 · · claude

Витрина переехала в ядро целиком: лендинг, три страницы под нишу, блог с 26 статьями, тарифы, обновления, карта сайта и robots. Всё в согласованном оформлении, всё с живыми числами из базы. Правки владельца по лендингу сделаны: счётчики убраны, шапка по центру, финал читается, витрина работ выросла до 15 роликов, механика провайдеров не палится. Числа моделей исправлены по факту каталога — 49 активных, 18 видео и 19 картинок; раньше стояли четыре из реестра ядра. ГЛАВНАЯ НАХОДКА: все 26 обложек блога битые — лежали на временном хранилище Kie, оно их удалило. Блог прода сейчас показывает статьи с пустыми картинками. Заменил типографскими карточками.

Владельцу на решение: (1) обложки блога — оставить типографику или перегенерить 26 штук (примерно 130 ₽); (2) категории блога заведены, но не проставлены ни одной статье — раскидать стоит, это структура для поиска; (3) ГЕО: нужно понять, что именно имеется в виду — городские страницы вида /moskva, упоминание городов в текстах или локальная разметка организации. От ответа зависит объём. Осталось по витрине: инструкции (в шапке кабинета есть ссылка), контакты, документация. Правовые страницы остаются на рабочем домене — на них ядро ссылается.

21.08.2026, 05:18:54 · · claude

Лендинг собран по согласованному макету и открывается на /lp: девять блоков, живая стена контента, витрина работ вкладками, тарифы прямо из базы. Главная работа была не в вёрстке, а в честности текста: макет писался с обещаниями рабочего сервиса, и ядро их не держит. 15+ моделей видео против четырёх, 10 соцсетей против пяти, клонирование голоса, которого нет. Всё это теперь подставляется живыми числами из реестра и ограничений тарифа — витрина едет за реальностью сама, а не расходится с ней молча.

Спринт С13 «Вид сервиса» закрыт полностью. Владельцу на решение: числа лендинга честные, но заметно скромнее прода (4 модели видео против 15). Либо дотягиваем состав моделей в ядре до прода, либо витрина так и говорит правду — решать до переключения. На день переключения записан шаг 4.1: перевести пять ссылок витрины на ядро.

21.08.2026, 04:38:18 · · claude

Публичные страницы: развилка закрыта в пользу развода по адресам — витрина остаётся на рабочем домене, кабинет уходит в ядро. По дороге нашлась дыра: ядро не ссылалось на оферту и политику нигде, то есть человек заводил кабинет и платил, не видя условий. Ссылки поставлены у кнопки регистрации и под кнопкой пополнения, адреса собраны в одну константу, правило закреплено проверкой в гейте — она следит и за наличием ссылок в коде, и за тем, что страницы ещё открываются.

По спринту С13 остался лендинг по согласованному макету — последняя задача. На день переключения записан шаг 4.1: перевести пять ссылок витрины со старого кабинета на ядро (Header.tsx и Footer.tsx рабочего сервиса), раньше трогать нельзя.