Пронин
Назад к блогу

Я полтора года защищал десктоп, а потом переписал приложение с Python на TypeScript — и понял, что спорил не про то

Почти два месяца назад я публично объяснял, почему для UTM-меток выбрал десктоп, а не веб. Теперь у того же инструмента есть веб-версия: открывается по ссылке, ничего не стоит, не просит ни почты, ни регистрации и намеренно не собирает о тебе никаких данных.

17 авг. 2026 г.

TL;DR

Почти два месяца назад я публично объяснял, почему для UTM-меток выбрал десктоп, а не веб. Теперь у того же инструмента есть веб-версия: открывается по ссылке, ничего не стоит, не просит ни почты, ни регистрации и намеренно не собирает о тебе никаких данных. Передумал я не про приватность — портативную версию тяжело поставить и нечем продвигать. А дешёвым переписывание с Python на TypeScript вышло по скучной причине: правила давно лежали отдельно от оболочки.

Закончил я прошлую статью про этот же инструмент разделом «Почему десктоп, а не свой сайт»: веб — это снова сервер, снова домен, снова база где-то в облаке, а значит снова «данные не у меня». Аргумент был мой, публичный и вполне уверенный. Потом я выкатил веб-версию того же инструмента, и открывается она по ссылке — без установки и без регистрации.

Если ты ту статью читал, вопрос напрашивается сам собой. Отвечаю без интриги: ни один из тех аргументов я не отменял, спорил я просто вообще не про то.

Зачем маркетологу свой инструмент, если готовых сервисов десятки

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

Только строка никогда и не была моей проблемой. Проблемой была гугл-таблица на двенадцать столбцов, которая открывалась при каждом запуске кампании и в которую ссылки складывал не я один: где-то опечатка, где-то параметров нет вовсе, а utm_source живёт сразу в трёх видах — google, Google и Google Ads. Расплачиваешься позже, когда цифры уже собраны, отчёт по источникам выглядит кашей, и надо руками соображать, какие строки вообще про один и тот же канал.

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

Один и тот же проект, три разных набора инструментов

Конец 2024-го. ChatGPT и Gemini тех лет прототип осилили — тогда я и познакомился с Flask и WebView, — но модель держала проект в голове ровно до третьего экрана, а дальше теряла связи между файлами, и правка в одном месте ломала соседнее. Firebase ненадолго дал ощущение, что всё получится, и на нём же всё и закончилось: базу данных я на гугловой инфраструктуре нормально разложить не смог. Кончилось буднично — закрыл папку и забыл, без всяких обещаний вернуться.

Январь 2026-го. Первый коммит стоит восьмым января, и сдвинулось дело не потому, что я за полтора года чему-то доучился. Просто появился Cursor — агент, который видит проект целиком, а не по одному файлу за раз, и вот эта способность держать всю структуру и была главным отличием от 2024-го. Самую муторную, финальную часть — работу с базой, сборки под Windows и macOS, автообновления — я дожимал уже с Claude: тридцать два коммита за одиннадцать дней и релизы 2.2.0 и 2.2.1 на выходе.

Август 2026-го. Тут планка выше: перенести уже выпущенный продукт на другой язык и под другую поверхность так, чтобы он вёл себя ровно как раньше. Пятьдесят четыре коммита за пять дней подряд — столько на момент, когда я пишу эти строки; часы git не показывает, и придумывать я их не стану. А дала мне агентская работа в этот раз прежде всего дисциплину решений: попросил агента посчитать записи в журнале — вышло 58 штук, у каждой дата и обоснование выбора.

Программистом я за эти полтора года не стал: как читал код, так и читаю. Изменилось то, на что этот навык опирается.

Что заставило меня передумать про веб

Красиво ответить тут легко: мол, дозрел до правильного разделения слоёв. Порядок был обратный, и слова «архитектура» в списке причин нет вообще.

  • Порог входа. Портативная сборка кажется мне простой ровно потому, что я с ней живу каждый день, а для многих людей она сложная. С установщиком не легче: страница релизов, выбор файла под свою систему, предупреждение системы (подписи я не покупаю ни под Windows, ни под macOS), установка и поиски, куда оно встало. Открыть ссылку — ноль решений.
  • Продвигать нечем. Мне не хватало адреса, который даёшь человеку и на этом заканчиваешь разговор.
  • Вход по фразе снял вопрос с базой. На облачной базе я утонул в 2024-м и повторять этот опыт не хотел. Теперь человек придумывает кодовую фразу из пяти русских слов и числа, на сервер уходит только её HMAC, почту я не спрашиваю, восстановления нет.
  • Дизайн. Старый интерфейс рисовался до того, как у меня появился свой сайт со своей дизайн-системой, и мне просто хотелось привести всё к общему виду.

Никакого «А чё будет…?» в этот раз не случилось. Было скучное «а как вообще человеку это отдать».

Как продукт переживает смену стека

Дальше скучная инженерная часть, ради которой историю и стоит рассказывать. Второй заход обошёлся дёшево из-за свойства, которое лежало в продукте с самого начала: экраны десктопного приложения были обычной веб-страницей. Тот же HTML, тот же CSS и тот же JavaScript, что на любом лендинге, только нарисованные в окне, а не во вкладке браузера. В прошлой статье я подавал это как приятное удобство для себя, а оказалось, что вся ставка проекта была именно здесь.

Второе свойство добавилось при переписывании. Раз логика всё равно переезжала с Python на TypeScript, я вынес её в отдельный пакет: ноль зависимостей, ноль сети, ноль обращений к странице, всё внешнее приходит параметром. Правило неудобное, зато только при нём один и тот же код проверяется в изоляции и уезжает внутрь оконного приложения без единой правки.

Как разложены слои

правила: что считать валидной меткой,

что именно развалит отчёт

ноль зависимостей · ноль сети · ноль DOM

│ импортирует

экраны: формы, таблицы, реплики помощника

│ импортирует

┌────────────┴────────────┐

веб-оболочка оболочка окна

Next.js в браузере Tauri на десктопе

Правила не знают ни про React, ни про сеть; экраны не знают, где именно они выполняются; оболочки знают про своё окружение всё, но ни одного правила внутри себя не держат. Складываешь эти два свойства — и вторая оболочка стоит уже совсем не столько, сколько слышится во фразе «переписал продукт на другой язык».

2.2, февраль 2026 → 3.0, август 2026

  • Оболочка: Flask в окне PyWebView → Next.js в браузере и Tauri в окне
  • Слой правил: 94 строки в одном файле → 2 892 строки в отдельном пакете
  • Как ты его получаешь: инсталлер на 17,9 МБ → инсталлер 6,5 МБ либо просто ссылка

Почему ядро выросло в тридцать раз

Я отправил Claude Code в замороженную папку со старой версией — посчитать, сколько там UTM-логики. Вышло 94 строки Python: три статических метода, и всё (перепроверил глазами, там правда только они). Новое ядро, посчитанное тем же способом, дало 2 892 строки TypeScript, хотя задача «взять адрес и приклеить к нему пять параметров» не изменилась ни на грамм.

Различие целиком в том, какое знание там теперь лежит. Старая версия умела склеить строку, новая понимает, что именно развалит отчёт, и говорит об этом словами последствий:

  • Facebook и facebook — два разных источника в отчёте, строки по ним не сложатся, а разойдутся на две;
  • utm_medium=organic на размеченной рекламной ссылке: расходы и переходы окажутся в разных строках, и посчитать окупаемость будет нечем;
  • два utm_source в одной ссылке: какое значение доедет до отчёта, решает уже сама аналитика, и у разных систем ответ разный.
Третий пункт я люблю больше всех, потому что он про класс ошибок, который проверка формата не ловит в принципе: ссылка синтаксически безупречна, метка стоит на месте, а данные врут.

Стережёт всё это набор из 235 тестов, все зелёные. Про качество кода цифра не говорит ничего, зато фиксирует одно: поведение старой версии описано письменно, и случайно его теперь не сломаешь. Пример из самых неочевидных — {keyword} в utm_term для Яндекс.Директа: площадка подставляет туда поисковую фразу сама, но если честно закодировать значение целиком, до неё доедет %7Bkeyword%7D и подстановка тихо не сработает. Поэтому скобки после кодирования возвращаются на место, и на этот кусок есть отдельный тест. Один пункт паритета я честно потерял: переключение RU/EN в десктопе 2.2 было, в вебе его нет — отложил осознанно, заранее замерив объём работы.

Что там есть, кроме склейки строки

Оговорюсь, чтобы не выглядело рекламой: генераторов UTM много, строку собирает каждый, и рынок я целиком не обходил. Ниже — только то, чего лично мне у них не попадалось и из-за чего я в своё время и полез собирать своё:

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

Отдельная моя любовь — справочник значений. В коде зашито 13 групп синонимов, я их там же и пересчитал: vk/vkontakte/vk_com, telegram/tg/tme/telega и дальше по списку. Поверх работает детектор расщепления, и формулирует он прямо: у тебя три написания одного источника, отчёты по ним не сойдутся. В версии 2.2 тут жила простая подсказка, чтобы не разводить зоопарк из fb, facebook и FB. Теперь зоопарк ловит механизм, и внимательность для этого больше не требуется.

Смысл всей затеи короткий: Excel-табличка тебе не нужна, и хранить ничего самому тоже не надо. Тем более что 3.0 живёт уже не только портативной сборкой — заводишь кодовую фразу, и шаблоны с историей лежат в базе сервиса, доступные с любой машины.

Почему это бесплатно и почему я выключил запись сессий

Сервис бесплатный, и это не «бесплатный тариф с лимитами» и не «первые десять ссылок». Я собирал его под себя и не вижу причин продавать то, что и так работает у меня на компьютере. Платный ресурс во всей системе ровно один — помощник на языковой модели, у него видимая дневная квота; кончилась квота, и инструмент просто работает дальше, без подсказок и без драмы.

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

Установщик, который снёс мою же старую версию

Чтобы не выглядело «сел и всё сразу заработало», расскажу про место, которое стоило мне больше нервов, чем весь остальной переезд. Установщик кладёт приложение в каталог, имя которого берётся из настройки сборки; у новой версии это имя совпало с папкой уже установленной 2.2, а Windows не различает регистр имён файлов. В итоге инсталлер 3.0 вычистил каталог целиком — вместе с исполняемым файлом старой версии и её деинсталлятором. Поймал я это у себя на живой установке, 2.2 поднял из опубликованного релиза, пользовательские данные уцелели, потому что лежат в другой папке профиля. Рассказываю подробно потому, что обе версии ОБЯЗАНЫ уживаться на одной машине: на косметический баг это не тянет никак, и виноват тут мой конфиг сборки.

Читая код, такое не увидишь. Оно вылезает, только когда ставишь, открываешь и щёлкаешь по живому — как и жёлтая полоса на пол-экрана, в которую в окне приложения превратилась еле заметная фоновая развёртка, безупречная в браузере.

Никого не уговариваю

Оглядываясь, понимаю, что весь тот спор держался на одном допущении: будто оболочка и есть продукт. Пока правила сидят внутри неё, так оно и работает, и выбирать действительно приходится. Когда правила вынесены наружу и закрыты тестами, от выбора остаётся бытовой вопрос — тебе сейчас удобнее вкладка или окно. Десктоп поэтому никуда и не делся: версия 3.0 вышла релизом в тот же день, что и веб, с базой на диске и без всякого входа.

Чем веб честно хуже — тоже скажу. В нём нет офлайна и нет переключения RU/EN. Нужно и то и другое — ставь приложение.

Уговаривать не буду. Генераторов UTM хватает, и если тот, которым ты пользуешься, задачу закрывает — отлично. Мой сделан под мои привычки: ничего не стоит, не просит аккаунта, держит шаблоны и историю в одном месте и особенно выручает, когда ссылок надо собрать сразу пачку. Потрогать можно прямо сейчас, без установки и без входа: конструктор UTM-меток. Код открыт и лежит на GitHub вместе с замороженной прошлой версией.

P.S.

Один вопрос, который мне правда интересен: где у тебя сейчас живут метки — в таблице, в сервисе или в голове? И если есть история про то, как отчёт наврал из-за разметки, расскажи: у меня в коллекции пока заглавная буква, лишний пробел и {keyword}, уехавший в отображаемую ссылку. Про внутренности переписывания тоже спрашивай, я их отсюда почти вычистил.

P.P.S. Если читаешь это не как разработчик, а как человек из бизнеса или маркетинга, которому хочется научиться так же доводить агента до результата, — у меня есть бесплатный курс Prompt.exe. 22 урока в 5 модулях, без регистрации. 😌

© 2026 Александр Пронин · цены не офертаконфиденциальностьсоглашениеcookie
ЛентаFreeБизнесуКурс