Я полтора года защищал десктоп, а потом переписал приложение с Python на TypeScript — и понял, что спорил не про то
Почти два месяца назад я публично объяснял, почему для UTM-меток выбрал десктоп, а не веб. Теперь у того же инструмента есть веб-версия: открывается по ссылке, ничего не стоит, не просит ни почты, ни регистрации и намеренно не собирает о тебе никаких данных.
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 модулях, без регистрации. 😌