<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Блог DartWay</title>
        <link>https://dartway.dev/ru/blog</link>
        <description>Full-stack Dart в проде: инженерные разборы, кейсы, релизы фреймворка.</description>
        <lastBuildDate>Thu, 01 Oct 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>ru</language>
        <copyright>Copyright © 2026 DartWay.</copyright>
        <item>
            <title><![CDATA[Shopify уходит с React Native: что это значит для Flutter]]></title>
            <link>https://dartway.dev/ru/blog/shopify-leaves-react-native</link>
            <guid>https://dartway.dev/ru/blog/shopify-leaves-react-native</guid>
            <pubDate>Thu, 01 Oct 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[В 2020 году Shopify перевёл свои приложения на React Native, а теперь возвращается к Swift и Kotlin — потому что агенты сделали вторую кодовую базу дешёвой. Как они это сделали, что остаётся за цифрами и почему Flutter-разработчикам не о чем беспокоиться.]]></description>
            <content:encoded><![CDATA[
<p>В 2020 году Shopify сделал полную ставку на React Native, в прошлом году
закончил переводить свои приложения — а в сентябре 2026-го объявил, что
возвращается к нативным Swift и Kotlin. Это не наш стек, но причина — кодовые
агенты, а агенты меняют то, что вообще важно при выборе фреймворка. Я делаю
фулстек-фреймворк на Dart и уже знаю, что приложения на нём буду писать не я:
их будут писать агенты. Это меняет цену каждого технического решения, и Shopify
— наглядный тому пример.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="что-произошло">Что произошло<a href="https://dartway.dev/ru/blog/shopify-leaves-react-native#%D1%87%D1%82%D0%BE-%D0%BF%D1%80%D0%BE%D0%B8%D0%B7%D0%BE%D1%88%D0%BB%D0%BE" class="hash-link" aria-label="Прямая ссылка на Что произошло" title="Прямая ссылка на Что произошло" translate="no">​</a></h2>
<p>В 2020 году Shopify поставил на React Native: одна кодовая база, меньше
издержек. В январе 2025-го их руководитель мобильной разработки писал, что у
React Native светлое будущее. 10 сентября 2026 года тот же человек опубликовал
<a href="https://shopify.engineering/back-to-native" target="_blank" rel="noopener noreferrer" class="">Native is now the future of mobile at Shopify</a>.
Приложение Shop уже переписано и лежит в сторах; основное приложение Shopify —
больше 300 экранов, виджеты, приложение для Apple Watch, Siri Shortcuts —
выйдет позже в этом году.</p>
<p>Они не говорят, что React Native был ошибкой. Они говорят, что в 2020 году это
был правильный выбор, а условия изменились.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="что-дал-нативный-код">Что дал нативный код<a href="https://dartway.dev/ru/blog/shopify-leaves-react-native#%D1%87%D1%82%D0%BE-%D0%B4%D0%B0%D0%BB-%D0%BD%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D1%8B%D0%B9-%D0%BA%D0%BE%D0%B4" class="hash-link" aria-label="Прямая ссылка на Что дал нативный код" title="Прямая ссылка на Что дал нативный код" translate="no">​</a></h2>
<p>Из <a href="https://shopify.engineering/shop-app-migration" target="_blank" rel="noopener noreferrer" class="">поста о миграции приложения Shop</a>:</p>















































<table><thead><tr><th></th><th>React Native</th><th>Нативно</th><th>Изменение</th></tr></thead><tbody><tr><td>Холодный старт, Android</td><td>4433 мс</td><td>2233 мс</td><td>−50%</td></tr><tr><td>Холодный старт, iOS</td><td>3200 мс</td><td>2466 мс</td><td>−23%</td></tr><tr><td>Размер приложения, Android</td><td>293 МБ</td><td>184 МБ</td><td>−37%</td></tr><tr><td>Размер приложения, iOS</td><td></td><td></td><td>+1 МБ</td></tr><tr><td>Сессии без падений</td><td>99,5%</td><td>99,95%</td><td>в 10 раз меньше падающих сессий</td></tr><tr><td>Время release-сборки под Android</td><td></td><td></td><td>−75%</td></tr></tbody></table>
<p>Шесть основных инженеров, двенадцать недель от прототипа до сторов. Сам
прототип занял у одного инженера одну неделю.</p>
<p>Преимущества нативного кода были известны всегда. Просто они всегда выглядели
мелочью — чуть быстрее запуск, чуть меньше сборка, на пару платформенных багов
меньше — на фоне стоимости того, чтобы делать всё дважды.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="почему-именно-сейчас">Почему именно сейчас<a href="https://dartway.dev/ru/blog/shopify-leaves-react-native#%D0%BF%D0%BE%D1%87%D0%B5%D0%BC%D1%83-%D0%B8%D0%BC%D0%B5%D0%BD%D0%BD%D0%BE-%D1%81%D0%B5%D0%B9%D1%87%D0%B0%D1%81" class="hash-link" aria-label="Прямая ссылка на Почему именно сейчас" title="Прямая ссылка на Почему именно сейчас" translate="no">​</a></h2>
<p>Держать две кодовые базы раньше означало держать две полноценные команды
разработки. Теперь большую часть кода пишут агенты, и они же могут взять на
себя обслуживающую работу: проверить, что фича ведёт себя одинаково на обеих
платформах, сравнить, верифицировать. Shopify формулирует это так:</p>
<blockquote>
<p>Native still means building and maintaining software on two platforms, that cost
has not disappeared. What changed is that agents can now do enough of the
implementation, translation, testing, and review work that it's no longer the
deciding factor it was in 2020.</p>
</blockquote>
<p>То же самое происходит везде. С агентами можно перенести почти любую библиотеку
на свой стек или переписать целый фреймворк — и сделать это быстро. Это меняет
то, что считается хорошим или плохим техническим решением.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="как-они-это-сделали">Как они это сделали<a href="https://dartway.dev/ru/blog/shopify-leaves-react-native#%D0%BA%D0%B0%D0%BA-%D0%BE%D0%BD%D0%B8-%D1%8D%D1%82%D0%BE-%D1%81%D0%B4%D0%B5%D0%BB%D0%B0%D0%BB%D0%B8" class="hash-link" aria-label="Прямая ссылка на Как они это сделали" title="Прямая ссылка на Как они это сделали" translate="no">​</a></h2>
<p>Интересно не само решение, а то, как доверить переписывание такого масштаба
агентам в компании, где любая ошибка стоит реальных денег. Они не просто
сказали модели переписать приложение. Они построили конвейер.</p>
<p><strong>Логика отделена от UI.</strong> Бизнес-логику отделили от интерфейса настолько
чисто, что она работает headless, на десктопной машине. Агенты тестировали её
через CLI и консольные тесты — посмотреть состояние приложения, перейти куда-то,
выполнить действие — за миллисекунды, не поднимая эмулятор. UI проверяли
отдельно: новые экраны сравнивали по скриншотам с работающим React
Native-приложением в тех же контрольных точках. Их инструмент для этого, Tardis,
ещё и собирал события аналитики в обоих приложениях, чтобы агенты могли сравнить
названия событий, их количество и полезную нагрузку.</p>
<p><strong>Маленькие чекпойнты.</strong> Их система Helix режет экран на маленькие
упорядоченные куски, каждый из которых можно отревьюить за минуты. Каждый кусок
должен подтвердить своё поведение тестами, совпасть с работающим приложением на
визуальном ревью, пройти двух состязательных код-ревьюеров и получить одобрение
человека — и только тогда попадает в коммит. Замечания с ревью запоминаются, так
что по ходу миграции циклу нужно всё меньше помощи.</p>
<p><strong>Где агенты не справились.</strong> Даже внутри такого конвейера сгенерированный код
приносил дублирование, архитектурный дрейф и проблемы с производительностью.
Shopify прямо говорит, что нативная экспертиза осталась необходимой — всё это
сработало только потому, что код ревьюила команда нативных экспертов.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="что-остаётся-за-цифрами">Что остаётся за цифрами<a href="https://dartway.dev/ru/blog/shopify-leaves-react-native#%D1%87%D1%82%D0%BE-%D0%BE%D1%81%D1%82%D0%B0%D1%91%D1%82%D1%81%D1%8F-%D0%B7%D0%B0-%D1%86%D0%B8%D1%84%D1%80%D0%B0%D0%BC%D0%B8" class="hash-link" aria-label="Прямая ссылка на Что остаётся за цифрами" title="Прямая ссылка на Что остаётся за цифрами" translate="no">​</a></h2>
<p>Несколько вещей в видео не поместились:</p>
<ul>
<li class=""><strong>Сравнение идёт со старой архитектурой React Native.</strong> В комментариях
отметили, что никто не знает, какие были бы цифры у New Architecture — а
переход на неё был крупным рефакторингом, который Shopify всё равно предстоял.</li>
<li class=""><strong>Это был перенос, а не новый продукт.</strong> У агентов было работающее
приложение, которое можно читать и с которым можно сравнивать. Shopify
говорит, что агенты работали лучше всего именно там, где была готовая
реализация; разработка чего-то нового так не ускоряется.</li>
<li class=""><strong>Две платформы теперь навсегда.</strong> Паритет функциональности «всё время», два
ревью, два релиза — и никаких обновлений по воздуху в обход стора.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="стоит-ли-беспокоиться-flutter-разработчикам">Стоит ли беспокоиться Flutter-разработчикам?<a href="https://dartway.dev/ru/blog/shopify-leaves-react-native#%D1%81%D1%82%D0%BE%D0%B8%D1%82-%D0%BB%D0%B8-%D0%B1%D0%B5%D1%81%D0%BF%D0%BE%D0%BA%D0%BE%D0%B8%D1%82%D1%8C%D1%81%D1%8F-flutter-%D1%80%D0%B0%D0%B7%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D1%87%D0%B8%D0%BA%D0%B0%D0%BC" class="hash-link" aria-label="Прямая ссылка на Стоит ли беспокоиться Flutter-разработчикам?" title="Прямая ссылка на Стоит ли беспокоиться Flutter-разработчикам?" translate="no">​</a></h2>
<p>Думаю, нет — я в этом вполне уверен.</p>
<p>Это ещё один пример того, как агенты делают возможным чуть больше. Крупнейшие
техкомпании всегда писали нативно, с параллельными командами под каждую
платформу, и в кроссплатформу не уходили. Shopify — большая компания, но не
настолько; агенты позволили ей работать по стандартам компаний с более сильными
командами разработки. Это тот же сдвиг, что и возможность небольшого бизнеса
сделать своё приложение, на которое раньше нужна была средняя компания.
Стоимость достижения того же результата падает — на каждом уровне.</p>
<p>И переписывание — это боль в любом случае. Большинству компаний оно просто не
стоит того: гораздо лучше вложить силы в продукт — новые фичи, более высокое
качество, — чем гоняться за последним процентом багов и выжимать мегабайты из
сборки. Выигрыш, который получил Shopify, для большинства команд значит куда
меньше.</p>
<p>Так что я не ожидаю, что это заметно сдвинет рынок труда, а Flutter — и того
меньше. На мой взгляд Flutter сильно впереди React Native, поэтому от таких
трендов страдает меньше.</p>
<p>Что я из этого забираю — тот же урок, который изменил мою собственную работу:
недавно я довольно радикально переписал свой фреймворк именно потому, что стал
доверять агентам очень большие задачи за короткое время. Говорит ли кто-то
вокруг вас о переходе с одного стека на другой с помощью агентов?</p>
<hr>
<p><em>Видеоверсия, на русском, — <a href="https://youtu.be/sbbvqOYI0Ko" target="_blank" rel="noopener noreferrer" class="">на YouTube</a>.</em></p>]]></content:encoded>
            <category>Ecosystem</category>
        </item>
        <item>
            <title><![CDATA[Dart MCP: что он на самом деле даёт кодовому агенту]]></title>
            <link>https://dartway.dev/ru/blog/dart-mcp-what-it-gives-an-agent</link>
            <guid>https://dartway.dev/ru/blog/dart-mcp-what-it-gives-an-agent</guid>
            <pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[MCP-сервер встроен в Dart SDK с июля 2025 года. Половина его инструментов повторяет то, что агент и так делает через терминал; вторая половина работает с запущенным приложением. Чего это стоит и во что обходится по контексту.]]></description>
            <content:encoded><![CDATA[
<p>MCP-сервер встроен в Dart SDK с июля 2025 года, а пользоваться им начали только
недавно. MCP — это протокол, по которому кодовый агент обращается к серверу за
информацией или действиями. Для Dart это значит, что агент больше не ограничен
терминалом — он может дотянуться до вашего запущенного приложения.</p>
<p>Мы подключили его к Claude Code на демо-приложении и посмотрели, что он даёт на
практике.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="подключение">Подключение<a href="https://dartway.dev/ru/blog/dart-mcp-what-it-gives-an-agent#%D0%BF%D0%BE%D0%B4%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D0%B5" class="hash-link" aria-label="Прямая ссылка на Подключение" title="Прямая ссылка на Подключение" translate="no">​</a></h2>
<p>Одна команда:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">claude mcp add --transport stdio dart -- dart mcp-server --enable all</span><br></div></code></pre></div></div>
<p>Без <code>--enable all</code> половина инструментов выключена. Внутри всё идёт через Dart
Tooling Daemon — тот же, которым для отладки пользуется DevTools. Следующая сессия
агента запускает сервер и получает список инструментов.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="что-агент-умел-и-раньше">Что агент умел и раньше<a href="https://dartway.dev/ru/blog/dart-mcp-what-it-gives-an-agent#%D1%87%D1%82%D0%BE-%D0%B0%D0%B3%D0%B5%D0%BD%D1%82-%D1%83%D0%BC%D0%B5%D0%BB-%D0%B8-%D1%80%D0%B0%D0%BD%D1%8C%D1%88%D0%B5" class="hash-link" aria-label="Прямая ссылка на Что агент умел и раньше" title="Прямая ссылка на Что агент умел и раньше" translate="no">​</a></h2>
<p>Анализатор, тесты, <code>dart fix</code>, форматирование, поиск по pub.dev. Всё полезное — и
всё то, что агент делает без сервера, через терминал и веб. В демо он пошёл искать
по pub.dev через инструмент; пакет он нашёл бы и без него.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="что-действительно-ново-запущенное-приложение">Что действительно ново: запущенное приложение<a href="https://dartway.dev/ru/blog/dart-mcp-what-it-gives-an-agent#%D1%87%D1%82%D0%BE-%D0%B4%D0%B5%D0%B9%D1%81%D1%82%D0%B2%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE-%D0%BD%D0%BE%D0%B2%D0%BE-%D0%B7%D0%B0%D0%BF%D1%83%D1%89%D0%B5%D0%BD%D0%BD%D0%BE%D0%B5-%D0%BF%D1%80%D0%B8%D0%BB%D0%BE%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5" class="hash-link" aria-label="Прямая ссылка на Что действительно ново: запущенное приложение" title="Прямая ссылка на Что действительно ново: запущенное приложение" translate="no">​</a></h2>
<p>Вот за что стоит держаться:</p>
<ul>
<li class=""><strong>ошибки времени выполнения</strong> из живого приложения — сразу агенту;</li>
<li class=""><strong>дерево виджетов</strong> в том виде, в каком оно в приложении прямо сейчас;</li>
<li class=""><strong>тапы и ввод текста</strong> через Flutter Driver — тапнуть, ввести текст, подождать,
получить смещение, снять скриншот;</li>
<li class=""><strong>вычисление выражения</strong> внутри запущенного приложения — агент может распечатать
значение на месте, а не искать нужное место в коде и добавлять print;</li>
<li class=""><strong>hot reload</strong> после правки.</li>
</ul>
<p>В демо агент вытащил ошибки времени выполнения, нашёл overflow в вёрстке, исправил
его и сделал hot reload — при этом ему не показывали, где проблема. Без MCP он не
увидел бы этих смещений и гадал бы.</p>
<p>Очевидное применение — то, которое масштабируется. Вы собрали большое приложение и
хотите проверить каждый экран. Обычно вы находите каждый overflow глазами, а потом
описываете: «в этом виджете overflow». С сервером агент сам обходит экраны и
собирает ошибки вёрстки. Или вы сами прокликиваете, а агент собирает ошибки через
MCP.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="чего-это-стоит">Чего это стоит<a href="https://dartway.dev/ru/blog/dart-mcp-what-it-gives-an-agent#%D1%87%D0%B5%D0%B3%D0%BE-%D1%8D%D1%82%D0%BE-%D1%81%D1%82%D0%BE%D0%B8%D1%82" class="hash-link" aria-label="Прямая ссылка на Чего это стоит" title="Прямая ссылка на Чего это стоит" translate="no">​</a></h2>
<p>Описания всех 24 инструментов попадают в контекст <strong>каждой</strong> сессии и перечитываются
на каждом шаге агента. Задача занимает от двадцати до нескольких сотен шагов, и
каждый тащит весь список — закешированный, но всё равно прочитанный и всё равно
занимающий контекст, внутри которого агенту приходится думать.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="вывод">Вывод<a href="https://dartway.dev/ru/blog/dart-mcp-what-it-gives-an-agent#%D0%B2%D1%8B%D0%B2%D0%BE%D0%B4" class="hash-link" aria-label="Прямая ссылка на Вывод" title="Прямая ссылка на Вывод" translate="no">​</a></h2>
<p>Для инструмента, который нужен на редком баге вёрстки, эта цена платится постоянно.
Поэтому в DartWay он не будет включён по умолчанию. Мы добавим его в фреймворк как
инструмент по требованию — подключается на прогон визуальных сценариев или на
починку багов вёрстки, и не присутствует в обычных сессиях разработки. Держать его
в каждой сессии — избыточно.</p>
<hr>
<p><em>Видеоверсия, на русском, — <a href="https://youtu.be/KRDMVBS_rig" target="_blank" rel="noopener noreferrer" class="">на YouTube</a>.</em></p>]]></content:encoded>
            <category>Ecosystem</category>
            <category>Engineering</category>
        </item>
        <item>
            <title><![CDATA[Serverpod 4 и App Studio: обзор от того, кто пишет код агентами]]></title>
            <link>https://dartway.dev/ru/blog/serverpod-4-review</link>
            <guid>https://dartway.dev/ru/blog/serverpod-4-review</guid>
            <pubDate>Tue, 29 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Fullstack hot reload, Postgres без Docker, MCP-сервер, офлайн-синхронизация, общие модели, облако и десктопная студия. Что это такое и насколько важно, когда основную часть кода пишут агенты в фоне.]]></description>
            <content:encoded><![CDATA[
<p>В сентябре вышел Serverpod 4, а вместе с ним — App Studio. Свой фреймворк мы с
Serverpod уже увели, но для fullstack Dart это по-прежнему крепкий выбор — так что
разберём релиз по пунктам: что каждая фича собой представляет и чего она стоит на
практике.</p>
<p>Важна оптика, поэтому начну с неё. Основная часть нашей разработки больше не
происходит перед запущенным приложением. Одна сессия ведёт разговор о работе; она
запускает отдельные сессии агентов — на разработку и на код-ревью; несколько задач
идут одновременно, в фоне; а результат смотрится на тестовом сервере пару раз в
день. Несколько ключевых фич Serverpod 4 сделаны под другую картину — разработчик
за столом, весь стек поднят локально.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="fullstack-hot-reload">Fullstack hot reload<a href="https://dartway.dev/ru/blog/serverpod-4-review#fullstack-hot-reload" class="hash-link" aria-label="Прямая ссылка на Fullstack hot reload" title="Прямая ссылка на Fullstack hot reload" translate="no">​</a></h2>
<p>База, сервер и Flutter-приложение поднимаются одной командой. Правка в любом
месте — от модели до экрана — подхватывается на лету, вместе с кодогенерацией, и
приложение сохраняет состояние. Больше никаких четырёх терминалов и перезапусков
руками.</p>
<p><strong>Стоит ли?</strong> Удобно — и почти не задевает то, как мы работаем. Когда агенты
работают параллельно, локального стека часто вообще нет: изменения уезжают на
тестовый сервер и проверяются там, и в большинстве случаев этого достаточно.
Поднять стек никогда не было сложной частью.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="postgres-без-docker-и-mcp-сервер">Postgres без Docker и MCP-сервер<a href="https://dartway.dev/ru/blog/serverpod-4-review#postgres-%D0%B1%D0%B5%D0%B7-docker-%D0%B8-mcp-%D1%81%D0%B5%D1%80%D0%B2%D0%B5%D1%80" class="hash-link" aria-label="Прямая ссылка на Postgres без Docker и MCP-сервер" title="Прямая ссылка на Postgres без Docker и MCP-сервер" translate="no">​</a></h2>
<p>Для локальной разработки теперь есть встроенный Postgres, так что Docker
опционален. А запущенное приложение поднимает MCP-сервер: твой кодовый агент
может залезть в живой стек — логи, миграции, работающее Flutter-приложение.</p>
<p><strong>Стоит ли?</strong> Полезно на редком сложном баге. Проблему, которая у нас реально
есть, это не решает: агент и так видит логи и на практике редко испытывает
недостаток информации, чтобы разобраться, что происходит. (Ту же идею мы смотрели
в собственном MCP-сервере Dart — <a class="" href="https://dartway.dev/ru/blog/dart-mcp-what-it-gives-an-agent">отдельный пост</a>.)</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="офлайн-и-база-на-клиенте">Офлайн и база на клиенте<a href="https://dartway.dev/ru/blog/serverpod-4-review#%D0%BE%D1%84%D0%BB%D0%B0%D0%B9%D0%BD-%D0%B8-%D0%B1%D0%B0%D0%B7%D0%B0-%D0%BD%D0%B0-%D0%BA%D0%BB%D0%B8%D0%B5%D0%BD%D1%82%D0%B5" class="hash-link" aria-label="Прямая ссылка на Офлайн и база на клиенте" title="Прямая ссылка на Офлайн и база на клиенте" translate="no">​</a></h2>
<p>Те же модели и ORM теперь генерируют SQLite во Flutter, а одна строка —
<code>database: sync</code> — включает двустороннюю синхронизацию с сервером.</p>
<p><strong>Стоит ли?</strong> Направление верное; DartWay идёт туда же. Пока помечено как
экспериментальное, и до зрелости ему нужно время.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="future-calls-и-общие-модели">Future calls и общие модели<a href="https://dartway.dev/ru/blog/serverpod-4-review#future-calls-%D0%B8-%D0%BE%D0%B1%D1%89%D0%B8%D0%B5-%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D0%B8" class="hash-link" aria-label="Прямая ссылка на Future calls и общие модели" title="Прямая ссылка на Future calls и общие модели" translate="no">​</a></h2>
<p>Фоновые задачи объявляются обычными методами Dart, поддерживаются и регулярные.
Модели могут жить в отдельном пакете, общем для сервера и Flutter-приложения.</p>
<p><strong>Стоит ли?</strong> Общие модели между фронтендом и бэкендом — известная боль, и это
была одна из причин, по которым мы ушли с Serverpod. Теперь это есть, и шаг
разумный.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="serverpod-cloud">Serverpod Cloud<a href="https://dartway.dev/ru/blog/serverpod-4-review#serverpod-cloud" class="hash-link" aria-label="Прямая ссылка на Serverpod Cloud" title="Прямая ссылка на Serverpod Cloud" translate="no">​</a></h2>
<p>Деплой бэкенда, базы и веб-приложения одной командой, от $5 в месяц. Фреймворк
бесплатный, бизнес — на хостинге; модель понятная.</p>
<p><strong>Стоит ли?</strong> Сегодня агент поднимает инфраструктуру за пару промптов: сервер,
Docker, база. А если ты не понимаешь, как устроен твой сервер, отдавать его
кому-то всё равно некомфортно.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="app-studio">App Studio<a href="https://dartway.dev/ru/blog/serverpod-4-review#app-studio" class="hash-link" aria-label="Прямая ссылка на App Studio" title="Прямая ссылка на App Studio" translate="no">​</a></h2>
<p>Десктопное приложение, внутри которого уже есть Flutter, Dart и Serverpod.
Своего AI в нём нет: подключаешь Claude Code, Cursor или любого другого агента.
На выходе — обычный проект, код у тебя на руках, а не закрытая песочница вроде
Lovable.</p>
<p><strong>Стоит ли?</strong> Две оговорки.</p>
<p>Первая: Serverpod даёт инструмент под каждую задачу, но не связывает их в
архитектуру. Чтобы хорошо собрать всё из этого списка, тебе всё равно придётся
быть архитектором своего приложения, иначе получится каша. Мы много раз на это
натыкались, когда строили на Serverpod напрямую, до появления DartWay.</p>
<p>Вторая важнее. Настоящая разработка не спотыкается о настройку окружения.
Прототип или первая версия делаются легко. Тяжело становится, когда экранов и
фич много и они должны оставаться согласованными между собой, а большой набор
требований должен выполняться точно. Окно чата рядом с запущенным приложением
здесь не помогает: где живут все требования и как это всё остаётся
синхронизированным? Именно этот вопрос решает, получится ли реальный продукт, и
App Studio на него не отвечает.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="вердикт">Вердикт<a href="https://dartway.dev/ru/blog/serverpod-4-review#%D0%B2%D0%B5%D1%80%D0%B4%D0%B8%D0%BA%D1%82" class="hash-link" aria-label="Прямая ссылка на Вердикт" title="Прямая ссылка на Вердикт" translate="no">​</a></h2>
<p>Хороший релиз хорошего фреймворка, и на него стоит посмотреть, если ты делаешь
fullstack Dart. Большая часть делает локальный цикл приятнее; сложные проблемы
разработки с агентами — архитектура и согласованность большого продукта —
оставлены тебе.</p>
<hr>
<p><em>Видеоверсия, на русском, — <a href="https://youtu.be/HL9ja3EwXzY" target="_blank" rel="noopener noreferrer" class="">на YouTube</a>.</em></p>]]></content:encoded>
            <category>Ecosystem</category>
        </item>
        <item>
            <title><![CDATA[Flutter 3.47: что ломается на самом деле и что сделать до ноября]]></title>
            <link>https://dartway.dev/ru/blog/flutter-3-47-what-breaks</link>
            <guid>https://dartway.dev/ru/blog/flutter-3-47-what-breaks</guid>
            <pubDate>Sun, 06 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[material и cupertino уехали из SDK. Сегодня ничего не ломается, миграция — это одна команда с двумя ловушками, а самое часто повторяемое утверждение про этот релиз — что ChangeNotifier переехал — пока неверно.]]></description>
            <content:encoded><![CDATA[
<p>За лето вышел ровно один стабильный релиз Flutter. 3.45 и 3.46 до stable вообще
не добрались, а 3.47 приехал 12 августа с 1358 коммитами поверх 3.44. Главное в
нём — не фича. Это переупаковка: <strong><code>material</code> и <code>cupertino</code> уехали из SDK в
отдельные пакеты.</strong></p>
<p>Звучит тревожно, но по большей части это не так — сегодня. Вот что мы выяснили,
мигрируя реальный проект с 3.44.0 на 3.47.2, и одно широко разошедшееся
утверждение про этот релиз, которое не выдерживает столкновения с кодом.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="сегодня-ничего-не-горит">Сегодня ничего не горит<a href="https://dartway.dev/ru/blog/flutter-3-47-what-breaks#%D1%81%D0%B5%D0%B3%D0%BE%D0%B4%D0%BD%D1%8F-%D0%BD%D0%B8%D1%87%D0%B5%D0%B3%D0%BE-%D0%BD%D0%B5-%D0%B3%D0%BE%D1%80%D0%B8%D1%82" class="hash-link" aria-label="Прямая ссылка на Сегодня ничего не горит" title="Прямая ссылка на Сегодня ничего не горит" translate="no">​</a></h2>
<p>Мы взяли живой проект, перевели его с 3.44.0 на 3.47.2 и запустили: <code>flutter analyze</code> чистый, тесты зелёные, библиотеки по-прежнему внутри SDK и не помечены
как deprecated. Депрекация назначена на <strong>ноябрьский релиз</strong>. Вот тогда часы и
пойдут.</p>
<p>Так что это хороший месяц для миграции и плохой месяц для паники. Окно
существует ровно затем, чтобы миграция прошла скучно.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="миграция--одна-команда-но-с-двумя-ловушками">Миграция — одна команда, но с двумя ловушками<a href="https://dartway.dev/ru/blog/flutter-3-47-what-breaks#%D0%BC%D0%B8%D0%B3%D1%80%D0%B0%D1%86%D0%B8%D1%8F--%D0%BE%D0%B4%D0%BD%D0%B0-%D0%BA%D0%BE%D0%BC%D0%B0%D0%BD%D0%B4%D0%B0-%D0%BD%D0%BE-%D1%81-%D0%B4%D0%B2%D1%83%D0%BC%D1%8F-%D0%BB%D0%BE%D0%B2%D1%83%D1%88%D0%BA%D0%B0%D0%BC%D0%B8" class="hash-link" aria-label="Прямая ссылка на Миграция — одна команда, но с двумя ловушками" title="Прямая ссылка на Миграция — одна команда, но с двумя ловушками" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token plain">dart fix --apply --code=migrate_design_widgets</span><br></div></code></pre></div></div>
<p><strong>Ловушка первая: простой <code>dart fix --apply</code> этого не сделает.</strong> Фикса нет в
наборе по умолчанию; без <code>--code=migrate_design_widgets</code> команда отработает,
отрапортует об успехе и не изменит ничего по делу. Это тот тип отказа, который
читается как «уже мигрировано».</p>
<p><strong>Ловушка вторая: она пишет <code>material_ui: any</code> в ваш <code>pubspec</code>.</strong> <code>any</code> значит,
что следующий ломающий релиз этого пакета приедет к вам в сборку молча. Замените
на каретку с версией сразу, как фикс отработает.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="утверждение-которое-неверно-changenotifier-никуда-не-уехал">Утверждение, которое неверно: <code>ChangeNotifier</code> никуда не уехал<a href="https://dartway.dev/ru/blog/flutter-3-47-what-breaks#%D1%83%D1%82%D0%B2%D0%B5%D1%80%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5-%D0%BA%D0%BE%D1%82%D0%BE%D1%80%D0%BE%D0%B5-%D0%BD%D0%B5%D0%B2%D0%B5%D1%80%D0%BD%D0%BE-changenotifier-%D0%BD%D0%B8%D0%BA%D1%83%D0%B4%D0%B0-%D0%BD%D0%B5-%D1%83%D0%B5%D1%85%D0%B0%D0%BB" class="hash-link" aria-label="Прямая ссылка на утверждение-которое-неверно-changenotifier-никуда-не-уехал" title="Прямая ссылка на утверждение-которое-неверно-changenotifier-никуда-не-уехал" translate="no">​</a></h2>
<p>Самая часто повторяемая фраза про этот релиз — что <code>ChangeNotifier</code> и компанию
вынесли из SDK в <code>package:listen</code>. В 3.47.2 произошло не это.</p>
<p>Классы по-прежнему в SDK и не помечены как deprecated. <code>listen</code> — это <strong>вторая,
независимая реализация тех же имён</strong>, а не новый дом для старых. Это разные
типы, и компилятор относится к ним именно так:</p>
<ul>
<li class="">передайте <code>ValueNotifier</code> из <code>listen</code> во flutter'овский
<code>ValueListenableBuilder</code> — и получите ошибку компиляции, а не тонкий баг в
рантайме;</li>
<li class="">то же самое в обратную сторону и для любой другой пары с общим именем.</li>
</ul>
<p>Где <code>listen</code> действительно к месту — код, в котором нет Flutter: сервер, CLI,
логика, общая для того и другого. Там типов из SDK никогда не было, а теперь есть
эквивалент. Внутри Flutter-приложения переход на него сегодня не даёт ничего и
стоит вам совместимости по типам с каждым виджетом, который ждёт версию из SDK.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="primary-constructors-сами-не-включатся">Primary constructors сами не включатся<a href="https://dartway.dev/ru/blog/flutter-3-47-what-breaks#primary-constructors-%D1%81%D0%B0%D0%BC%D0%B8-%D0%BD%D0%B5-%D0%B2%D0%BA%D0%BB%D1%8E%D1%87%D0%B0%D1%82%D1%81%D1%8F" class="hash-link" aria-label="Прямая ссылка на Primary constructors сами не включатся" title="Прямая ссылка на Primary constructors сами не включатся" translate="no">​</a></h2>
<p>В Dart 3.13 приехали primary constructors, и они за флагом версии языка. Без
<code>sdk: ^3.13.0</code> в <code>pubspec.yaml</code> вы получите <code>experiment_not_enabled</code> даже на
правильном SDK — тулчейн в порядке, это проект не просит.</p>
<p>Стоит, впрочем, сказать прямо: ценность экономии на бойлерплейте конструкторов
сильно падает, когда этот бойлерплейт пишет не человек. Если ваши конструкторы
генерируются, это изменение про читаемость, а не про продуктивность.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="остальное">Остальное<a href="https://dartway.dev/ru/blog/flutter-3-47-what-breaks#%D0%BE%D1%81%D1%82%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5" class="hash-link" aria-label="Прямая ссылка на Остальное" title="Прямая ссылка на Остальное" translate="no">​</a></h2>
<p>Impeller по умолчанию на десктопе, iOS 15 и macOS 12 как новые нижние границы и
работа над Wasm — всё реальное, ничего срочного, и ни на что из ваших дел в этом
месяце не влияет.</p>
<p><strong>Что реально сделать до ноября:</strong> прогнать миграцию в ветке, нормально
запинить <code>material_ui</code> и не трогать <code>ChangeNotifier</code>.</p>
<hr>
<p><em>Проверено миграцией живого проекта с 3.44.0 на 3.47.2 — <code>before/</code> и <code>after/</code>
собираются и проходят тесты каждый на своём SDK. Видеоразбор —
<a href="https://youtu.be/kNXMVEj_7bA" target="_blank" rel="noopener noreferrer" class="">на YouTube</a>.</em></p>]]></content:encoded>
            <category>Ecosystem</category>
            <category>Engineering</category>
        </item>
        <item>
            <title><![CDATA[Один аккаунт, две двери: во что на самом деле обходится вход по телефону или почте]]></title>
            <link>https://dartway.dev/ru/blog/one-account-two-doors</link>
            <guid>https://dartway.dev/ru/blog/one-account-two-doors</guid>
            <pubDate>Sat, 05 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Разрешить входить хоть по номеру телефона, хоть по почте выглядит правкой на одну строку. На деле это правка модели данных, и счёт приходит дублями аккаунтов, которые не слить.]]></description>
            <content:encoded><![CDATA[
<p>Разрешить людям входить <strong>хоть</strong> по номеру телефона, <strong>хоть</strong> по почте выглядит
правкой на одну строку: расширяем поиск, принимаем оба варианта. Мы выкатили это
на прошлой неделе в живом проекте, и правка на одну строку оказалась правкой
модели данных — со счётом в придачу.</p>
<p>Вот во что она обходится на самом деле, в том порядке, в каком приходят счета.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="поиск-никогда-не-был-проблемой">Поиск никогда не был проблемой<a href="https://dartway.dev/ru/blog/one-account-two-doors#%D0%BF%D0%BE%D0%B8%D1%81%D0%BA-%D0%BD%D0%B8%D0%BA%D0%BE%D0%B3%D0%B4%D0%B0-%D0%BD%D0%B5-%D0%B1%D1%8B%D0%BB-%D0%BF%D1%80%D0%BE%D0%B1%D0%BB%D0%B5%D0%BC%D0%BE%D0%B9" class="hash-link" aria-label="Прямая ссылка на Поиск никогда не был проблемой" title="Прямая ссылка на Поиск никогда не был проблемой" translate="no">​</a></h2>
<p>Фреймворк хранил одну колонку <code>userIdentifier</code> и искал по строгому равенству.
Попроси его найти профиль по телефону <em>или</em> почте — и расширять просто негде: не
потому что запрос сложный, а потому что сама аутентификация требует, чтобы эта
колонка существовала. Убери её — и ядро падает на старте.</p>
<p>Так что первая правка — не «искать по двум полям». Это <strong>дать приложению право
голоса в том, как находится пользователь</strong>:</p>
<div class="language-dart codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dart codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#F8F8F2"><span class="token class-name">DwAuthConfig</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  findUserProfileByIdentifier</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">session</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> identifier</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)">// приложение само решает, что значит «тот же самый человек»</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></div></code></pre></div></div>
<p>Колонка перестаёт быть обязательной, идентификатор приходит уже нормализованным,
а фреймворк перестаёт делать вид, что знает, что такое личность в вашей предметной
области. Три обязанности переезжают в приложение, и документация говорит об этом
прямым текстом — резолвер, который молча делает не то, хуже, чем отсутствие
резолвера.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="поиск-по-двум-полям-сам-по-себе-ничего-не-чинит">Поиск по двум полям сам по себе ничего не чинит<a href="https://dartway.dev/ru/blog/one-account-two-doors#%D0%BF%D0%BE%D0%B8%D1%81%D0%BA-%D0%BF%D0%BE-%D0%B4%D0%B2%D1%83%D0%BC-%D0%BF%D0%BE%D0%BB%D1%8F%D0%BC-%D1%81%D0%B0%D0%BC-%D0%BF%D0%BE-%D1%81%D0%B5%D0%B1%D0%B5-%D0%BD%D0%B8%D1%87%D0%B5%D0%B3%D0%BE-%D0%BD%D0%B5-%D1%87%D0%B8%D0%BD%D0%B8%D1%82" class="hash-link" aria-label="Прямая ссылка на Поиск по двум полям сам по себе ничего не чинит" title="Прямая ссылка на Поиск по двум полям сам по себе ничего не чинит" translate="no">​</a></h2>
<p>Вот эта часть нас и удивила, и ради неё написан этот пост.</p>
<p>У пользователя, который зарегистрировался по телефону, <strong>колонка с почтой пустая</strong>.
Поиск по обоим полям не находит ничего нового, потому что во втором поле ничего
нет. Поиск одновременно корректен и бесполезен.</p>
<p>Чтобы заполнить эту колонку, нужен способ привязать второй канал к существующему
аккаунту — а значит, <code>addAuthProvider</code> и <code>changeIdentifier</code> должны реально
работать. У нас оба бросали <code>UnimplementedError</code>, и экрана для этого не было ни в
одном макете. Пока его нет, <strong>вход с другого канала по-прежнему тихо создаёт второй
аккаунт</strong>, ровно как и раньше.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="дубли-уже-есть">Дубли уже есть<a href="https://dartway.dev/ru/blog/one-account-two-doors#%D0%B4%D1%83%D0%B1%D0%BB%D0%B8-%D1%83%D0%B6%D0%B5-%D0%B5%D1%81%D1%82%D1%8C" class="hash-link" aria-label="Прямая ссылка на Дубли уже есть" title="Прямая ссылка на Дубли уже есть" translate="no">​</a></h2>
<p>Дальше идёт то, что отложить не получится. Мы пошли добавлять уникальные индексы на
<code>phone</code> и <code>email</code> и обнаружили, что <strong>уникальных индексов не было вообще</strong> — ни на
новых колонках, ни на старом <code>userIdentifier</code>. Две строки с одинаковым значением
уже были легальны, и поиск возвращал ту, которую базе захочется отдать.</p>
<p>Индекс не добавить, пока дубли не слиты. А дубли существуют именно <em>потому что</em>
вход со второго канала раньше создавал новый аккаунт. Баг и его разгребание — один
и тот же объект.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="слияние--это-событие-а-не-действие">Слияние — это событие, а не действие<a href="https://dartway.dev/ru/blog/one-account-two-doors#%D1%81%D0%BB%D0%B8%D1%8F%D0%BD%D0%B8%D0%B5--%D1%8D%D1%82%D0%BE-%D1%81%D0%BE%D0%B1%D1%8B%D1%82%D0%B8%D0%B5-%D0%B0-%D0%BD%D0%B5-%D0%B4%D0%B5%D0%B9%D1%81%D1%82%D0%B2%D0%B8%D0%B5" class="hash-link" aria-label="Прямая ссылка на Слияние — это событие, а не действие" title="Прямая ссылка на Слияние — это событие, а не действие" translate="no">​</a></h2>
<p>Слияние аккаунтов необратимо: один профиль поглощает другой, и после этого никто не
восстановит, что откуда пришло. Поэтому мы описали его как <strong>событие</strong> <code>ProfileMerge</code>,
а не как вызов метода — кто слил, кого и что переехало. Действие оставляет базу
консистентной, а историю потерянной; событие оставляет и то, и другое.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="что-мы-сказали-бы-себе-неделю-назад">Что мы сказали бы себе неделю назад<a href="https://dartway.dev/ru/blog/one-account-two-doors#%D1%87%D1%82%D0%BE-%D0%BC%D1%8B-%D1%81%D0%BA%D0%B0%D0%B7%D0%B0%D0%BB%D0%B8-%D0%B1%D1%8B-%D1%81%D0%B5%D0%B1%D0%B5-%D0%BD%D0%B5%D0%B4%D0%B5%D0%BB%D1%8E-%D0%BD%D0%B0%D0%B7%D0%B0%D0%B4" class="hash-link" aria-label="Прямая ссылка на Что мы сказали бы себе неделю назад" title="Прямая ссылка на Что мы сказали бы себе неделю назад" translate="no">​</a></h2>
<ul>
<li class="">«Вход по телефону или почте» — задача не про экран авторизации. Это задача про
<strong>идентичность профиля</strong>, а экран — последние пять процентов.</li>
<li class="">Выкатывайте <strong>привязку второго канала</strong> в том же релизе. Без неё фича — это поиск,
который ничего не находит.</li>
<li class="">Проверяйте уникальные индексы <strong>до</strong> того, как что-то пообещали. У нас их не было
за всю жизнь проекта, и никто не заметил, потому что ничто их не требовало.</li>
<li class="">Проверяйте на живой базе полным кругом: регистрация по телефону, привязка почты,
вход по почте в тот же аккаунт, одна строка в таблице. У нас 23 серверных теста,
включая этот проход; версия этой статьи, написанная по чтению кода, была бы неверна
в двух местах.</li>
</ul>
<p>Со стороны фреймворка это приехало в ядро <code>dartway</code> <code>0.12.1</code> — резолвер аутентификации
и работающий <code>changeIdentifier</code>. Если вы на более ранней версии, интересная часть —
миграция, а не API.</p>]]></content:encoded>
            <category>Engineering</category>
            <category>Case breakdown</category>
        </item>
    </channel>
</rss>