Работа в четыре руки: дизайн-форк Ильи
Verstak пишу не я один. Параллельно работает Илья — дизайнер, который ведёт свой форк (verstak_rayner, в коммитах метятся как rayner). Я держу логику и инфраструктуру, он — внешний вид, UX-полировку и установщик. Это не теория «как делить работу», а реальная история про то, как два контрибьютора не наступают друг другу на ноги.
Зоны: src/ vs electron/
Грубое, но рабочее разделение:
- Илья →
src/— компоненты, темы, стили, UX-потоки (справка, черновики, FilesView, фирменный установщик Nord UI, оверхол апдейтера). - Я →
electron/— провайдеры, тулзы, agent loop, storage, коннекторы, IPC.
Граница не идеальна (апдейтер живёт в electron/, а UX-часть его — в src/), но она задаёт «чья эта проблема по умолчанию». Когда зоны пересекаются, побеждает тот, у кого больше контекста по конкретному модулю.
Как мерджим без боли
Слияние форка Ильи (filatov/main) дало релизы 1.5.8, 1.5.15, 1.5.17. Конфликты были предсказуемые, и почти все — в двух местах:
- Changelog. Обе стороны добавляют записи в начало массива → конфликт почти гарантирован. Решение механическое: комбинируем оба набора записей, шов
],},{, без дублей. Это случилось дважды (1.5.15 и merge Ильи) — и оба раза лечится одинаково.
- Общие типы. Илья добавил
streamStartedAtвSessionSnapshot— мой тестproject-store-lifecycleупал, потому что его фикстура не знала про новое поле. Илья завернулfetchJsonв{data, rateLimit}— мои вызовы release-notes читали сырой результат. Оба раза фикс — выровнять на новый контракт, а не откатывать чужое.
Правило, которое держит мир
Из регламента проекта, и я ему следую жёстко: не удалять чужой неиспользуемый код без явной просьбы. Когда я делал точечную чистку мёртвого кода, ts-prune показал кучу «неиспользуемых» экспортов — но часть из них была кодом Ильи, который он ещё доведёт. Я проверял каждый grep'ом перед удалением и трогал только то, что мёртво и моё. Чужой незавершённый код — не мусор, а work-in-progress.
Был и забавный момент с ts-prune: он флагнул useT, ToolCall, OnboardingWizard, ReviewButton, SkillPicker как неиспользуемые. Все они используются — через ре-экспорты и JSX. Линтер не видит JSX-использования и реэкспортов. Урок: автоматический «мёртвый код» — это гипотеза, а не приговор. Каждую проверяй руками.
Разделение, которое работает
Связка получилась честная: я не лезу в его пиксели, он не лезет в мою стейт-машину. Там, где надо состыковаться (новое поле в общем типе, новый IPC), — стыкуемся через контракт, а не через переписывание. Для этого и существуют TypeScript strict и тесты: они превращают «ой, я не знал, что ты это поменял» в красный билд за секунду.
→ Дальше: Удалённые проекты: правки прямо на сервере по SSH