Rotas e navegação
Num app Fayz você não escreve um roteador. A navegação — os itens de menu e as telas por trás deles — é derivada do que o manifesto declara: os plugins ligados na surface e as páginas extras que você definir. Esta página mostra de onde cada rota vem.
A surface é a raiz
Toda rota nasce dentro de uma surface. Uma surface é uma face do app (admin, storefront, member); ela declara o scaffold (o layout do shell), a lista de plugins e uma lista opcional de pages:
{
"surfaces": {
"admin": {
"scaffold": "admin",
"plugins": [{ "id": "dashboard" }, { "id": "crm" }],
"pages": []
}
}
}
Do bloco acima o runtime monta a navegação inteira. Você liga um plugin e o menu e as telas dele aparecem — sem tocar em nenhum arquivo de rotas.
De onde vem a rota de um plugin
Cada plugin descreve a própria navegação e as próprias rotas no seu manifesto (o PluginManifest). São dois campos:
navigation— os itens de menu. Cada entrada temsection(main,secondaryousettings),position(ordem),label,routee umiconopcional. É isso que pinta a barra lateral.routes— o mapeamentopath → componente. Cada rota aponta para um componente (direto ou porcomponentIddo registry) e pode declarar umguard(authenticated,public,role,share-token).
Quando você adiciona { "id": "crm" } à surface, o runtime lê o navigation e o routes do CRM e injeta ambos: o item do menu e a tela em /crm. Ligar dois plugins junta as duas navegações na mesma barra lateral, ordenadas por section + position. É por isso que a ordem dos ids no manifesto não define a ordem visual — quem define é o position de cada plugin.
Páginas próprias, sem plugin
Quando você precisa de uma tela avulsa que não pertence a nenhum plugin, declare-a em pages. Cada página é dado puro e aponta para exatamente uma fonte de conteúdo:
{
"pages": [
{ "path": "/sobre", "label": "Sobre", "section": "secondary",
"blocks": [{ "type": "hero", "props": { "title": "Nossa história" } }] },
{ "path": "/relatorio", "label": "Relatório", "component": "custom:harvest-board" }
]
}
O schema exige uma de três chaves na página:
blocks— uma árvore de blocos resolvida pelo registry de blocos (recompor conteúdo sem código).entity— o id de uma entidade, para gerar um CRUD sobre ela.component— o id de um componente próprio registrado nosrc/registry.tsx(namespacecustom:).
Os campos label, icon, section e permission controlam como a página aparece no menu e quem a enxerga.
O registry conecta ids a código
Rotas e páginas referenciam código por id, nunca por import inline. Um component: "custom:harvest-board" no manifesto só resolve porque você registrou esse id no src/registry.tsx:
// src/registry.tsx
import { registerPage } from '@fayz-ai/core'
import { HarvestBoard } from './pages/HarvestBoard'
registerPage('custom:harvest-board', HarvestBoard)
registerPage (e as suas irmãs registerComponent / registerBlock) vêm de @fayz-ai/core — esse import compila hoje. O src/lib/fayz-runtime.ts gerado pelo scaffold ainda é uma tela-âncora e não reexporta essas funções; ele vai reexportá-las em breve. Até lá, importe direto de @fayz-ai/core.
Essa separação — dado no manifesto, código no registry — é o que mantém o app atualizável: a plataforma sabe exatamente quais ids você sobrescreveu e o fayz doctor consegue reportar divergências.
Regra prática: capacidade = ligar um plugin (o menu cresce sozinho); tela avulsa = uma entrada em pages; código próprio = um id custom: no registry.tsx, referenciado como dado. Você nunca edita um roteador.
Próximo: entenda o arquivo inteiro em Configuração.