Pular para o conteúdo principal

Manifest mode — declaring screens the analyzer cannot read

appilots sync builds the application map by reading your source for React Navigation: a <NavigationContainer> and <Stack.Screen> / <Tab.Screen> declarations. That covers most React Native apps, but not all of them.

If your app does not use React Navigation, the analyzer finds nothing. The most common case by far is Expo Router — the default for npx create-expo-app — which declares routes as files, not as JSX elements the analyzer can see. A file-based router, a custom navigator, or a web app driven by something other than React Router all land in the same place.

Since the map is what tells the agent which screens exist and how to reach them, an empty map means an agent that cannot navigate anywhere.

The CLI fails on this now

appilots generate and appilots sync exit non-zero when the map has zero screens, and the error names both likely causes. Previously the sync succeeded and uploaded an empty map, which made the dashboard look integrated when it was not. If an empty map is genuinely what you want, pass --allow-empty.

The escape hatch

Declare the screens yourself in appilots.manifest.json at the project root:

{
"version": "1",
"screens": [
{
"name": "ItemList",
"description": "Lista os itens do estoque",
"route": "/itens",
"actions": [{ "id": "new-item", "type": "navigate", "label": "Novo item" }]
},
{
"name": "ItemForm",
"description": "Cadastra ou edita um item do estoque",
"route": "/itens/novo",
"forms": [
{
"id": "item",
"fields": [
{ "id": "sku", "label": "SKU", "type": "text", "required": true },
{ "id": "model", "label": "Modelo", "type": "text", "required": true }
]
}
],
"actions": [{ "id": "save", "type": "submit", "label": "Salvar", "effect": "create" }]
}
],
"navigation": {
"initialScreen": "ItemList"
}
}

Screens here use the same shape the analyzer produces, so a manifest and analyzer output merge without translation. That matters for two reasons:

  • You can declare only what the analyzer missed. The manifest is merged on top of whatever was found, not instead of it — a hybrid app declares its odd screens and lets the analyzer keep the rest.
  • A fully declared app needs no React Navigation at all.

Point at a different file with manifestPath in .appilotsrc.

A manifest that exists but does not parse — bad JSON, a screen missing name — is a hard error, not a skipped file. A typo must not quietly contribute zero screens; that is the same silence this page exists to remove.

What the manifest does not do

It declares structure, not runtime state. The agent still reads the live screen through the SDK's snapshot, so a manifest does not remove the need for AppilotsProvider, and it does not make the SDK track the current screen.

Screen tracking is separate: AppilotsNavigationContainer handles it for React Navigation. Without React Navigation, call setCurrentScreen() yourself when your router changes route — otherwise currentScreen stays null and the agent does not know where it is, even with a perfect manifest.

import { setCurrentScreen } from '@appilots/sdk';
import { usePathname } from 'expo-router';

function ScreenTracker() {
const pathname = usePathname();
useEffect(() => {
// The name must match the screen's `name` in your manifest.
setCurrentScreen(routeNameFor(pathname));
}, [pathname]);
return null;
}

The peer dependency warning

@appilots/sdk declares @react-navigation/native as a peer dependency, because that is the path the SDK is built around. If you are driving Appilots from a manifest instead, your package manager will print one unmet-peer warning on install. That is expected, and it is deliberate: the warning is there so the much larger group of people who meant to use React Navigation find out at install time rather than after a clean sync produced an empty map.