Когда команда впервые просит LLM «сгенерируй экран на Flutter», в ответ приходит виджет с захардкоженным списком внутри build и навигацией через Navigator.push прямо в кнопке. То же самое происходит с React Native: экран, где useState живёт в каждом компоненте, а fetch вызывается в useEffect без отмены. Модель не виновата — виноват промт, в котором не сказано, где хранится состояние, как устроен роутинг и что считать тестом.
Ниже — подборка промтов, которые я использую в реальной работе над двумя кодовыми базами: Flutter (Dart, Riverpod, go_router) и React Native (TypeScript, Zustand, React Navigation). Критерий выбора простой. Если у команды сильная JS/TS-база и нужна быстрая интеграция с веб-кодом — берите блок для React Native. Если важны единый рендеринг, предсказуемый UI и производительность на слабых устройствах — блок для Flutter.
Навигация: где LLM чаще всего хардкодит
Проблема навигации в том, что модель по умолчанию использует самый простой API: Navigator.push в Flutter и navigation.navigate в React Native. Это работает в демо и ломается в проде, когда нужны deep links, восстановление стека и защита от повторного перехода.
Промт для Flutter (go_router)
Ты — Flutter-разработчик. Спроектируй навигацию через go_router для приложения с экранами: каталог, карточка товара, корзина, профиль.
Требования:
1. Маршруты — константы в отдельном файле, без строк в коде виджетов.
2. Вложенные маршруты: каталог -> карточка товара через path 'product/:id'.
3. Deep link /product/42 должен открывать карточку и позволять вернуться в каталог.
4. Покажи redirect для неавторизованного пользователя на /login с сохранением исходного пути.
5. Никаких Navigator.push внутри виджетов. Только context.go / context.push.
Выведи: файл маршрутов, пример GoRouter, пример перехода из виджета, объясни, что произойдёт при повторном переходе на /product/42.
В ответе важно проверить пункт про повторный переход: go_router по умолчанию добавляет страницу в стек, и если пользователь дважды тапнет по карточке, получит две копии. В промте это можно закрыть явно — попросить context.pushReplacement или проверку текущего location.
Промт для React Native (React Navigation)
Ты — React Native разработчик на TypeScript. Спроектируй навигацию на React Navigation (native stack) для тех же экранов: каталог, карточка товара, корзина, профиль.
Требования:
1. Типизируй RootStackParamList, экраны и хуки useNavigation/useRoute без any.
2. Deep link: схема app://product/:id, конфиг linking с getStateFromPath.
3. Условный старт: если токена нет — стартовый экран Login, после логина — возврат на сохранённый маршрут.
4. Не вызывай navigation.navigate в useEffect без условия — объясни, как избежать двойного перехода.
Выведи: типы, NavigationContainer с linking, пример экрана каталога, обработку возврата после логина.
Здесь ловушка — useEffect с навигацией: модель любит писать useEffect(() => navigation.navigate('Cart'), []), и при быстром ремаунте экран прыгает дважды. Просите явно показать guard.
State management: где LLM плодит дубли
Вторая типичная ошибка — состояние размазано по компонентам. В Flutter модель кладёт setState в StatefulWidget, в React Native — useState в каждый экран, и корзина на двух экранах живёт двумя копиями.
Промт для Flutter (Riverpod)
Ты — Flutter разработчик, используешь Riverpod 2.x без codegen.
Задача: состояние корзины, доступное каталогу, карточке и бейджу в AppBar.
Требования:
1. StateNotifier или Notifier для корзины: add, remove, clear, total.
2. Провайдеры: cartProvider, cartCountProvider (производный), checkoutProvider (FutureProvider).
3. Покажи, как экран читает состояние через ref.watch и меняет через ref.read(...).notifier.
4. Объясни, почему нельзя вызывать ref.read в build.
5. Тест: unit-тест Notifier без виджетов.
Выведи: класс Notifier, провайдеры, пример виджета бейджа, тест.
Проверяйте пункт 4 в ответе: если модель написала ref.read в build — это красный флаг, при пересборке виджета состояние не обновится, и бейдж застынет на старом числе.
Промт для React Native (Zustand)
Ты — React Native разработчик. Состояние корзины на Zustand + TypeScript.
Требования:
1. Store: items, addItem, removeItem, clear, селектор total и count.
2. Селекторы — отдельными функциями, чтобы компонент не ререндерился при изменении чужого поля.
3. Покажи компонент бейджа с useCartStore(state => state.count) и объясни, почему нельзя возвращать весь store.
4. Персистентность: persist middleware с AsyncStorage, partialize только items.
5. Тест стора без React: вызовы экшенов и проверка селекторов.
Выведи: store, селекторы, бейдж, тест.
Самая частая ошибка здесь — useCartStore() без селектора. Модель так пишет по умолчанию, и любой чих в сторе ререндерит весь экран.
| Стек | Инструмент | Что фиксируем в промте |
|---|---|---|
| Flutter | Riverpod | ref.watch в build, ref.read в колбэках |
| Flutter | Bloc | события и состояния отдельными классами |
| React Native | Zustand | селекторы, persist, отсутствие ререндеров |
| React Native | Redux Toolkit | слайсы, типизация PayloadAction |
API и анимации: где промт экономит день
Сетевой слой — третье место, где LLM халтурит: fetch без отмены, без таймаута и без обработки ошибок. Промт должен требовать явный контракт.
Ты — мобильный разработчик. Слой API для каталога товаров.
Flutter-вариант: dio + retrofit-подобный клиент, отмена запроса при dispose экрана, таймаут 10 с, маппинг ошибок в sealed-класс Result<T>.
React Native-вариант: fetch с AbortController, отмена в cleanup useEffect, таймаут через AbortSignal.timeout, тот же Result<T> на TypeScript.
Требования к обоим:
1. Тип ответа Product { id, title, price }.
2. Ошибки: сеть, таймаут, 401, 5xx — разные ветки.
3. Покажи экран со списком, где отмена реально срабатывает при быстром переходе назад.
4. Объясни, что произойдёт, если ответ придёт после dispose.
Пункт 4 — неочевидный, но важный: в Flutter после dispose setState бросит исключение, а в React Native обновление состояния размонтированного компонента тихо проигнорируется. Разное поведение — разная стратегия проверки.
Анимации лучше просить отдельным промтом с требованием указать, что происходит на слабом устройстве: попросите модель оценить, сколько кадров она ожидает, и покажите, где используется AnimatedBuilder во Flutter или useNativeDriver в React Native.
Тесты: промт, который закрывает ветки
Последний блок — тесты. Без явного требования модель пишет один happy-path тест и останавливается.
Ты — инженер по тестам. Для модуля корзины напиши тесты.
Flutter: unit-тест Riverpod Notifier через ProviderContainer, без pumpWidget. Покрой: добавление, удаление, очистку, total при пустой корзине, повторное добавление того же товара.
React Native: тест Zustand-стора без React Testing Library — прямые вызовы экшенов и проверка селекторов.
Требования: не менее 6 тестов, каждый с явным ожидаемым результатом, без сна и таймеров. Покажи, какая ветка покрыта каждым тестом.
Как выбрать стек под промты
Выбор не про «что лучше», а про то, какой промт ваша команда сможет поддерживать. Если в команде есть веб-фронтенд на React и общий TypeScript-код — React Native-промты дадут быстрый старт и переиспользование типов. Если UI критичен, а устройства слабые — Flutter-промты с фиксацией дерева виджетов и Riverpod дают предсказуемый рендеринг.
Мой рабочий приём: держите промты в репозитории рядом с кодом, в папке prompts/, и обновляйте их, когда меняется версия библиотеки. Промт — такой же артефакт, как тест. Если модель начала выдавать Navigator.push вместо context.go, значит, промт устарел, а не модель сломалась.
Начните с одного промта — навигации — и прогоните двойной тап. Если стек не дублируется и deep link открывает экран, остальные блоки подтянутся быстрее. Больше разборов инструментов и подходов к работе с LLM — в LLM-инженерия с LangChain.