@evertonferreira96/agents 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/api.md +45 -0
- package/.claude/agents/app.md +41 -0
- package/.claude/agents/architect.md +49 -0
- package/.claude/agents/infra.md +27 -0
- package/.claude/agents/product.md +58 -0
- package/.claude/agents/quick.md +33 -0
- package/.claude/agents/security.md +39 -0
- package/.claude/agents/ui.md +54 -0
- package/.claude/agents/verifier.md +56 -0
- package/.claude/agents/web.md +41 -0
- package/.claude/hooks/config-update-check.mjs +14 -0
- package/.claude/hooks/context-budget.mjs +51 -0
- package/.claude/hooks/graphify-update.sh +16 -0
- package/.claude/hooks/guard-bash.sh +64 -0
- package/.claude/hooks/orchestrator-guard.sh +49 -0
- package/.claude/hooks/post-compact.sh +26 -0
- package/.claude/hooks/readonly-guard.sh +54 -0
- package/.claude/hooks/stop-verify.sh +52 -0
- package/.claude/rules/api.md +21 -0
- package/.claude/rules/db.md +9 -0
- package/.claude/rules/delivery-diagram.md +16 -0
- package/.claude/rules/git-workflow.md +34 -0
- package/.claude/rules/graphify.md +3 -0
- package/.claude/rules/mobile.md +22 -0
- package/.claude/rules/tickets.md +9 -0
- package/.claude/rules/ui.md +14 -0
- package/.claude/rules/web.md +27 -0
- package/.claude/settings.json +114 -0
- package/.claude/skills/animate/RECIPES.md +324 -0
- package/.claude/skills/animate/SKILL.md +208 -0
- package/.claude/skills/animate/agents/openai.yaml +5 -0
- package/.claude/skills/animate-expo/RECIPES.md +385 -0
- package/.claude/skills/animate-expo/SKILL.md +264 -0
- package/.claude/skills/animate-expo/agents/openai.yaml +5 -0
- package/.claude/skills/animation-performance/SKILL.md +223 -0
- package/.claude/skills/animation-performance/agents/openai.yaml +5 -0
- package/.claude/skills/apple-design/SKILL.md +291 -0
- package/.claude/skills/apple-design/agents/openai.yaml +5 -0
- package/.claude/skills/arch-api/SKILL.md +291 -0
- package/.claude/skills/arch-api/agents/openai.yaml +3 -0
- package/.claude/skills/arch-api/references/common.md +174 -0
- package/.claude/skills/arch-api/references/contract.md +289 -0
- package/.claude/skills/arch-api/references/domain.md +301 -0
- package/.claude/skills/arch-api/references/endpoints.md +184 -0
- package/.claude/skills/arch-api/references/review.md +228 -0
- package/.claude/skills/arch-api/references/security.md +151 -0
- package/.claude/skills/arch-app/SKILL.md +278 -0
- package/.claude/skills/arch-app/agents/openai.yaml +3 -0
- package/.claude/skills/arch-app/references/navigation-session.md +257 -0
- package/.claude/skills/arch-app/references/review.md +155 -0
- package/.claude/skills/arch-app/references/screens.md +214 -0
- package/.claude/skills/arch-app/references/services.md +301 -0
- package/.claude/skills/arch-app/references/ui-mobile.md +124 -0
- package/.claude/skills/arch-db/SKILL.md +181 -0
- package/.claude/skills/arch-db/agents/openai.yaml +3 -0
- package/.claude/skills/arch-db/references/indexes-queries.md +251 -0
- package/.claude/skills/arch-db/references/migrations.md +191 -0
- package/.claude/skills/arch-db/references/modeling.md +214 -0
- package/.claude/skills/arch-db/references/mysql.md +262 -0
- package/.claude/skills/arch-db/references/pooling-ops.md +112 -0
- package/.claude/skills/arch-db/references/review.md +143 -0
- package/.claude/skills/arch-db/references/transactions-locks.md +141 -0
- package/.claude/skills/arch-web/SKILL.md +307 -0
- package/.claude/skills/arch-web/agents/openai.yaml +3 -0
- package/.claude/skills/arch-web/references/auth-session-env.md +165 -0
- package/.claude/skills/arch-web/references/blocks.md +198 -0
- package/.claude/skills/arch-web/references/cache.md +124 -0
- package/.claude/skills/arch-web/references/review.md +142 -0
- package/.claude/skills/arch-web/references/services.md +297 -0
- package/.claude/skills/arch-web/references/tests.md +159 -0
- package/.claude/skills/arch-web/references/ui-forms.md +143 -0
- package/.claude/skills/break-ui/CATALOG.md +135 -0
- package/.claude/skills/break-ui/SKILL.md +176 -0
- package/.claude/skills/break-ui/agents/openai.yaml +5 -0
- package/.claude/skills/brief/SKILL.md +91 -0
- package/.claude/skills/brief/agents/openai.yaml +3 -0
- package/.claude/skills/diagnosing-bugs/SKILL.md +138 -0
- package/.claude/skills/diagnosing-bugs/agents/openai.yaml +3 -0
- package/.claude/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +44 -0
- package/.claude/skills/emil-design-eng/SKILL.md +675 -0
- package/.claude/skills/emil-design-eng/agents/openai.yaml +5 -0
- package/.claude/skills/find-animation-opportunities/SKILL.md +141 -0
- package/.claude/skills/find-animation-opportunities/agents/openai.yaml +5 -0
- package/.claude/skills/graphify/.graphify_version +1 -0
- package/.claude/skills/graphify/SKILL.md +742 -0
- package/.claude/skills/graphify/agents/openai.yaml +3 -0
- package/.claude/skills/graphify/references/add-watch.md +56 -0
- package/.claude/skills/graphify/references/exports.md +91 -0
- package/.claude/skills/graphify/references/extraction-spec.md +70 -0
- package/.claude/skills/graphify/references/github-and-merge.md +46 -0
- package/.claude/skills/graphify/references/hooks.md +33 -0
- package/.claude/skills/graphify/references/query.md +317 -0
- package/.claude/skills/graphify/references/transcribe.md +52 -0
- package/.claude/skills/graphify/references/update.md +221 -0
- package/.claude/skills/grill-me/SKILL.md +7 -0
- package/.claude/skills/grill-me/agents/openai.yaml +5 -0
- package/.claude/skills/grill-with-docs/SKILL.md +64 -0
- package/.claude/skills/grill-with-docs/agents/openai.yaml +5 -0
- package/.claude/skills/grilling/SKILL.md +30 -0
- package/.claude/skills/grilling/agents/openai.yaml +3 -0
- package/.claude/skills/handoff/SKILL.md +28 -0
- package/.claude/skills/handoff/agents/openai.yaml +5 -0
- package/.claude/skills/impeccable/SKILL.md +89 -0
- package/.claude/skills/impeccable/agents/openai.yaml +5 -0
- package/.claude/skills/impeccable/reference/adapt.md +318 -0
- package/.claude/skills/impeccable/reference/adapt.native.md +58 -0
- package/.claude/skills/impeccable/reference/android.md +46 -0
- package/.claude/skills/impeccable/reference/animate.md +89 -0
- package/.claude/skills/impeccable/reference/audit.md +137 -0
- package/.claude/skills/impeccable/reference/audit.native.md +139 -0
- package/.claude/skills/impeccable/reference/bolder.md +33 -0
- package/.claude/skills/impeccable/reference/clarify.md +94 -0
- package/.claude/skills/impeccable/reference/colorize.md +86 -0
- package/.claude/skills/impeccable/reference/component-review.md +63 -0
- package/.claude/skills/impeccable/reference/craft-floor.md +44 -0
- package/.claude/skills/impeccable/reference/craft.md +5 -0
- package/.claude/skills/impeccable/reference/critique.md +809 -0
- package/.claude/skills/impeccable/reference/degraded/asset-producer.md +42 -0
- package/.claude/skills/impeccable/reference/degraded/documenter.md +24 -0
- package/.claude/skills/impeccable/reference/degraded/finish-reviewer.md +38 -0
- package/.claude/skills/impeccable/reference/degraded/manual-edit-applier.md +92 -0
- package/.claude/skills/impeccable/reference/delight.md +70 -0
- package/.claude/skills/impeccable/reference/distill.md +111 -0
- package/.claude/skills/impeccable/reference/doctor.md +54 -0
- package/.claude/skills/impeccable/reference/document.md +416 -0
- package/.claude/skills/impeccable/reference/extract.md +69 -0
- package/.claude/skills/impeccable/reference/generate.md +101 -0
- package/.claude/skills/impeccable/reference/harden.md +345 -0
- package/.claude/skills/impeccable/reference/hooks.md +113 -0
- package/.claude/skills/impeccable/reference/init.md +131 -0
- package/.claude/skills/impeccable/reference/ios.md +51 -0
- package/.claude/skills/impeccable/reference/layout.md +84 -0
- package/.claude/skills/impeccable/reference/live-setup.md +104 -0
- package/.claude/skills/impeccable/reference/live.md +325 -0
- package/.claude/skills/impeccable/reference/mode-operate.md +21 -0
- package/.claude/skills/impeccable/reference/mode-persuade.md +19 -0
- package/.claude/skills/impeccable/reference/mode-read.md +21 -0
- package/.claude/skills/impeccable/reference/new-work.md +154 -0
- package/.claude/skills/impeccable/reference/onboard.md +234 -0
- package/.claude/skills/impeccable/reference/operate.md +61 -0
- package/.claude/skills/impeccable/reference/optimize.md +258 -0
- package/.claude/skills/impeccable/reference/overdrive.md +127 -0
- package/.claude/skills/impeccable/reference/polish.md +105 -0
- package/.claude/skills/impeccable/reference/quieter.md +99 -0
- package/.claude/skills/impeccable/reference/region-map.md +26 -0
- package/.claude/skills/impeccable/reference/routing.md +24 -0
- package/.claude/skills/impeccable/reference/shape.md +59 -0
- package/.claude/skills/impeccable/reference/typeset.md +80 -0
- package/.claude/skills/impeccable/reference/visualize.md +47 -0
- package/.claude/skills/impeccable/scripts/VERSION +1 -0
- package/.claude/skills/impeccable/scripts/command-metadata.json +98 -0
- package/.claude/skills/impeccable/scripts/data/font-index-failures.json +121 -0
- package/.claude/skills/impeccable/scripts/data/font-index.json +1 -0
- package/.claude/skills/impeccable/scripts/impeccable +206 -0
- package/.claude/skills/impeccable/scripts/impeccable.cmd +214 -0
- package/.claude/skills/impeccable/scripts/live-browser-dom.js +281 -0
- package/.claude/skills/impeccable/scripts/live-browser-ignores.js +242 -0
- package/.claude/skills/impeccable/scripts/live-browser-session.js +148 -0
- package/.claude/skills/impeccable/scripts/live-browser.js +13532 -0
- package/.claude/skills/impeccable/scripts/modern-screenshot.umd.js +14 -0
- package/.claude/skills/improve-animations/AUDIT.md +115 -0
- package/.claude/skills/improve-animations/PLAN-TEMPLATE.md +73 -0
- package/.claude/skills/improve-animations/SKILL.md +110 -0
- package/.claude/skills/improve-animations/agents/openai.yaml +5 -0
- package/.claude/skills/mobile-native/SKILL.md +311 -0
- package/.claude/skills/mobile-native/agents/openai.yaml +3 -0
- package/.claude/skills/orchestrate/SKILL.md +181 -0
- package/.claude/skills/orchestrate/agents/openai.yaml +3 -0
- package/.claude/skills/pick-ui-library/SKILL.md +85 -0
- package/.claude/skills/pick-ui-library/agents/openai.yaml +5 -0
- package/.claude/skills/ponytail/SKILL.md +52 -0
- package/.claude/skills/ponytail/agents/openai.yaml +3 -0
- package/.claude/skills/ponytail-audit/SKILL.md +103 -0
- package/.claude/skills/ponytail-audit/agents/openai.yaml +3 -0
- package/.claude/skills/ponytail-debt/SKILL.md +44 -0
- package/.claude/skills/ponytail-debt/agents/openai.yaml +3 -0
- package/.claude/skills/ponytail-review/SKILL.md +101 -0
- package/.claude/skills/ponytail-review/agents/openai.yaml +3 -0
- package/.claude/skills/postgres-best-practices/SKILL.md +42 -0
- package/.claude/skills/postgres-best-practices/agents/openai.yaml +3 -0
- package/.claude/skills/postgres-best-practices/references/backup-restore.md +372 -0
- package/.claude/skills/postgres-best-practices/references/bulk-loading.md +315 -0
- package/.claude/skills/postgres-best-practices/references/connection-pooling.md +310 -0
- package/.claude/skills/postgres-best-practices/references/hot-standby.md +221 -0
- package/.claude/skills/postgres-best-practices/references/indexing.md +318 -0
- package/.claude/skills/postgres-best-practices/references/logical-replication.md +610 -0
- package/.claude/skills/postgres-best-practices/references/major-version-upgrades.md +334 -0
- package/.claude/skills/postgres-best-practices/references/performance-diagnostics.md +553 -0
- package/.claude/skills/postgres-best-practices/references/query-optimization.md +294 -0
- package/.claude/skills/postgres-best-practices/references/query-patterns.md +566 -0
- package/.claude/skills/postgres-best-practices/references/schema-design.md +362 -0
- package/.claude/skills/postgres-best-practices/references/security-roles.md +489 -0
- package/.claude/skills/postgres-best-practices/references/transaction-isolation.md +344 -0
- package/.claude/skills/project-docs/SKILL.md +330 -0
- package/.claude/skills/project-docs/agents/openai.yaml +3 -0
- package/.claude/skills/prototype/PICKER.md +197 -0
- package/.claude/skills/prototype/SKILL.md +98 -0
- package/.claude/skills/prototype/agents/openai.yaml +5 -0
- package/.claude/skills/retro/SKILL.md +43 -0
- package/.claude/skills/retro/agents/openai.yaml +5 -0
- package/.claude/skills/review/SKILL.md +74 -0
- package/.claude/skills/review/agents/openai.yaml +3 -0
- package/.claude/skills/review/references/smells.md +23 -0
- package/.claude/skills/review-animations/SKILL.md +120 -0
- package/.claude/skills/review-animations/STANDARDS.md +187 -0
- package/.claude/skills/review-animations/agents/openai.yaml +5 -0
- package/.claude/skills/tdd/SKILL.md +38 -0
- package/.claude/skills/tdd/agents/openai.yaml +3 -0
- package/.claude/skills/tdd/mocking.md +59 -0
- package/.claude/skills/tdd/tests.md +77 -0
- package/.claude/skills/triage/SKILL.md +58 -0
- package/.claude/skills/triage/agents/openai.yaml +3 -0
- package/.claude/skills/ui-ux-pro-max/SKILL.md +378 -0
- package/.claude/skills/ui-ux-pro-max/agents/openai.yaml +5 -0
- package/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.claude/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.claude/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/.claude/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.claude/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.claude/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.claude/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/.claude/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.claude/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.claude/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/.claude/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.claude/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/.claude/skills/writing-for-agents/SKILL-MECHANICS.md +22 -0
- package/.claude/skills/writing-for-agents/SKILL.md +81 -0
- package/.claude/skills/writing-for-agents/agents/openai.yaml +3 -0
- package/.worktreeinclude +3 -0
- package/AGENTS.md +44 -0
- package/README.md +14 -2
- package/VERSION +1 -0
- package/bin/agents.mjs +45 -0
- package/docs/adr/README.md +31 -0
- package/docs/brief-template.md +3 -0
- package/docs/plans/README.md +73 -0
- package/package.json +46 -4
- package/scripts/bootstrap.mjs +188 -0
- package/scripts/check-catalog.mjs +43 -0
- package/scripts/skills-update.mjs +51 -0
- package/skills-lock.json +269 -0
- package/templates/CLAUDE.project.md +41 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: arch-app
|
|
3
|
+
description: "Implementa, revisa e refatora apps React Native Expo: boundaries navigation/screens/modules/common, grupos de tela em `src/screens/(public|private)/` com screen fina e `modules.tsx`, services como application layer que devolvem `ServiceResponse` e chamam o client OpenAPI gerado via request autenticado, entities derivadas do contrato, gateways só para fonte fora do OpenAPI, sessão em `expo-secure-store`, env `EXPO_PUBLIC_*` validado com Zod, observabilidade, tema/tokens e testes de regra pura. Use ao criar ou revisar telas, navigators, rotas e deep links, features em `src/modules`, services, auth/sessão, env ou testes de qualquer app Expo. Não use para APIs NestJS (`arch-api`) nem para o web Next.js (`arch-web`); visual e design system vão para `/ui`."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# arch-app
|
|
7
|
+
|
|
8
|
+
Arquitetura de apps React Native Expo: telas agrupadas e finas, services que são o caso de uso e falam com o client OpenAPI gerado, sessão segura no dispositivo, env validado e observabilidade desde o primeiro service.
|
|
9
|
+
|
|
10
|
+
```text
|
|
11
|
+
App compõe providers e o root navigator; navigator registra rotas.
|
|
12
|
+
Screen é fina: compõe `Module.*` do seu grupo e, quando orquestra, um controller hook.
|
|
13
|
+
Module interage: estado de UI, chama o service, trata `status`, dá feedback.
|
|
14
|
+
Service é o caso de uso: regra, mock, request autenticado, `ServiceResponse`.
|
|
15
|
+
Entities derivam do contrato gerado: a API é a fonte única do contrato.
|
|
16
|
+
`common` não conhece produto.
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Fluxo: `App -> root.navigator -> <nome>.screen -> Module.* -> service -> authedRequest -> api.<org>.<api>.client.<operationId> -> API`.
|
|
20
|
+
|
|
21
|
+
Placeholders: `<pm>`, `<org>`, `<api>`, `<feature>`, `<acao>`, `<screen>`, `<secao>`; exemplos usam `invoices` e `customers`. ADRs e `AGENTS.md` do repo vencem esta skill.
|
|
22
|
+
|
|
23
|
+
## Assunto -> reference
|
|
24
|
+
|
|
25
|
+
| Abra quando for | Reference |
|
|
26
|
+
|---|---|
|
|
27
|
+
| criar ou alterar service, entities, domains, schemas, store da feature, mock, gateway ou query | [references/services.md](references/services.md) |
|
|
28
|
+
| criar ou alterar uma tela: grupo, `modules.tsx`, module, controller hook, service local, Safe Area, teclado, lista, permissão de dispositivo | [references/screens.md](references/screens.md) |
|
|
29
|
+
| mexer em boot, navigator, rota, params, deep link, login/logout/refresh, `secureStorage` ou env | [references/navigation-session.md](references/navigation-session.md) |
|
|
30
|
+
| mexer em componente comum, tema/tokens, marca, formulário, acessibilidade, performance ou observabilidade | [references/ui-mobile.md](references/ui-mobile.md) |
|
|
31
|
+
| testar, revisar PR, propor ADR ou fechar o critério de entrega | [references/review.md](references/review.md) |
|
|
32
|
+
|
|
33
|
+
## Antes de editar
|
|
34
|
+
|
|
35
|
+
Levante no repo, nesta ordem:
|
|
36
|
+
|
|
37
|
+
```txt
|
|
38
|
+
1. package.json do app e da raiz: pm, scripts reais, lint/test vindos do monorepo
|
|
39
|
+
2. versões: Expo, React Native, React Navigation ou expo-router, Zod, TanStack Query, store
|
|
40
|
+
3. aliases: tsconfig.json, babel.config.js, metro.config.js
|
|
41
|
+
4. App.tsx e index.ts: ordem de boot, providers raiz, overlays globais
|
|
42
|
+
5. src/navigation: root/public/private, types.ts, linking.ts, hook de bootstrap
|
|
43
|
+
6. screens.md (inventário de telas) e um grupo vizinho em src/screens/(private)/(<screen>)/
|
|
44
|
+
7. uma feature vizinha em src/modules/<feature>: services, entities, domains
|
|
45
|
+
8. common/services (ServiceResponse, api-error, dedupe, mappers comuns) e common/observability
|
|
46
|
+
9. common/openapi/api.ts (fachada), types.ts e openapi-ts.config.ts (input e output)
|
|
47
|
+
10. sessão: auth store, authedRequest, secureStorage, settings.storage.keys
|
|
48
|
+
11. env: common/env/app.env.ts, .env.example, modo mock
|
|
49
|
+
12. UI comum: common/components, common/theme, common/hooks, common/permissions
|
|
50
|
+
13. testes existentes (*.unit.spec.ts) e onde o runner está configurado
|
|
51
|
+
14. ADRs e AGENTS.md do app
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Pronto quando cada item tem um caminho no repo ou "não existe". O que não existe não é importado; script ausente vira lacuna declarada, não comando inventado.
|
|
55
|
+
|
|
56
|
+
### Onde o repo varia
|
|
57
|
+
|
|
58
|
+
| Ponto | Padrão desta skill | Repo diferente: siga o repo |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| Navegação | React Navigation em `src/navigation` | expo-router: `app/_layout.tsx` faz o papel do navigator ([navigation-session.md](references/navigation-session.md)) |
|
|
61
|
+
| Fachada do client | `api.<org>.<api>.{client, settings}` em `common/openapi/api.ts` | outro nome: o critério é um ponto único para `baseUrl`, `cache` e headers globais |
|
|
62
|
+
| Request autenticado | `authedRequest` em `modules/auth/services/` | helper do repo; sem helper, o service lê o token do `secureStorage` |
|
|
63
|
+
| Modo mock | `environment.DATA_SOURCE_MODE === "MOCK"` | sem modo mock: o service não tem o ramo |
|
|
64
|
+
| Store | zustand em `*.store.hook.ts` | store do repo |
|
|
65
|
+
| Cache de servidor | TanStack Query (`AppQueryProvider`) | ausente: estado local + service; adotar pede ADR |
|
|
66
|
+
| Chaves de storage | `settings.storage.keys` | registro de chaves do repo |
|
|
67
|
+
| Testes | `*.unit.spec.ts` ao lado, runner do monorepo | outro runner ou nenhum: [review.md](references/review.md) |
|
|
68
|
+
| Marca dinâmica | `common/brand` | ausente: tokens do tema |
|
|
69
|
+
| `code` de erro | UPPER_SNAKE | formato do repo |
|
|
70
|
+
|
|
71
|
+
## Mapa
|
|
72
|
+
|
|
73
|
+
```txt
|
|
74
|
+
App.tsx index.ts app.config.ts app.json openapi-ts.config.ts screens.md (inventário de telas)
|
|
75
|
+
src/
|
|
76
|
+
navigation/
|
|
77
|
+
root.navigator.tsx splash até sessão e bootstrap resolverem; Public x Private; providers do fluxo privado
|
|
78
|
+
public.navigator.tsx private.navigator.tsx settings.navigator.tsx tab-routes.tsx
|
|
79
|
+
linking.ts types.ts use-app-bootstrap.hook.ts components/
|
|
80
|
+
screens/(public)/(<screen>)/ screens/(private)/(<screen>)/
|
|
81
|
+
<nome>.screen.tsx uma ou mais telas do grupo; fina: layout, safe area, Module.*
|
|
82
|
+
modules.tsx export const Module = { <Secao>: <Nome>Module }
|
|
83
|
+
modules/<secao>/<nome>.module.tsx composição visual da seção
|
|
84
|
+
modules/<secao>/*.component.tsx componente local da tela
|
|
85
|
+
hooks/use-<x>.hook.ts controller da tela: estado e chamadas a services, quando a tela orquestra vários
|
|
86
|
+
services/<x>.service.ts service LOCAL só quando a regra é exclusiva desta tela
|
|
87
|
+
modules/<feature>/
|
|
88
|
+
services/<acao>.<feature>.service.ts application layer: authedRequest + api.<org>.<api>.client.<operationId>; ServiceResponse
|
|
89
|
+
(algumas features usam <acao>-<objeto>.service.ts; ver "Nomes")
|
|
90
|
+
entities/<feature>.entities.ts namespace <Feature>.Model / .DTO derivado de Api.<Org>.<Api>.*
|
|
91
|
+
domains/<feature>.domain.ts (+ .unit.spec.ts) regra pura
|
|
92
|
+
schemas/<feature>.schemas.ts Zod para input de formulário e deep link
|
|
93
|
+
hooks/<x>.store.hook.ts hooks/use-<x>.hook.ts estado da feature compartilhado por telas
|
|
94
|
+
components/<x>.component.tsx componente de domínio usado por 2+ telas
|
|
95
|
+
mappers/<x>.mapper.ts tradução entre API e UI
|
|
96
|
+
mocks/ dados do modo mock
|
|
97
|
+
gateways/<x>.gateway.ts EXCEÇÃO: fonte fora do contrato OpenAPI (fetch nativo, nunca lança)
|
|
98
|
+
common/
|
|
99
|
+
components/<nome>/<nome>.component.tsx + index.ts
|
|
100
|
+
env/app.env.ts hooks/{feedbacks,loading,query,pagination,motion}/ observability/ permissions/ brand/ realtime/
|
|
101
|
+
openapi/api.ts client.ts types.ts <org>/<api>/setup/ (gerado)
|
|
102
|
+
providers/app.providers.tsx
|
|
103
|
+
services/service-response.ts api-error.ts in-flight-dedupe.ts
|
|
104
|
+
settings/{routes,storage,times} storage/secure-storage.ts theme/{default.theme.ts, tokens/}
|
|
105
|
+
utils/<tipo>/<nome>.<tipo>.util.ts (+ .unit.spec.ts)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Colocação, do mais local ao mais compartilhado:
|
|
109
|
+
|
|
110
|
+
- Usado por uma tela: fica no grupo `screens/(…)/(<screen>)/` (`modules/`, `hooks/`, `services/`).
|
|
111
|
+
- Regra, chamada ou tipo usado por 2+ telas: `modules/<feature>/`. Feature é domínio, não tela.
|
|
112
|
+
- Componente de domínio usado por 2+ telas: `modules/<feature>/components/`.
|
|
113
|
+
- Visual sem domínio, reutilizável: `common/components/<nome>/`.
|
|
114
|
+
- Infra sem produto (client, env, storage, observabilidade, tema, permissões): `common/`.
|
|
115
|
+
- Promover é mover no segundo consumidor; até lá, local.
|
|
116
|
+
|
|
117
|
+
## Camadas
|
|
118
|
+
|
|
119
|
+
| Camada | Arquivo | Responsabilidade |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| App | `App.tsx`, `index.ts` | ordem de boot, providers raiz, root navigator, overlays globais (status bar, loader, toast) |
|
|
122
|
+
| Navigation | `src/navigation/*` | rotas, ParamLists, linking, bootstrap de sessão, gate public/private |
|
|
123
|
+
| Screen | `<nome>.screen.tsx` | fina: Safe Area, layout, `Module.*`, controller hook; despacha variante por param |
|
|
124
|
+
| Mapa local | `modules.tsx` | `export const Module = { ... }` com os modules do grupo |
|
|
125
|
+
| Module | `modules/<secao>/<nome>.module.tsx` | interação da seção: estado de UI, chama service, trata `status`, feedback |
|
|
126
|
+
| Componente local | `modules/<secao>/<nome>.component.tsx`, `.modal.tsx` | visual da tela: props entram, eventos saem |
|
|
127
|
+
| Controller | `hooks/use-<x>.hook.ts` (no grupo) | estado e efeitos da tela (foco, polling, realtime), chama services, devolve dados e ações |
|
|
128
|
+
| Service local | `services/<x>.service.ts` (no grupo) | view model e chamadas exclusivas do grupo, com o contrato do service da feature |
|
|
129
|
+
| Service da feature | `services/<acao>.<feature>.service.ts` | um caso de uso por arquivo: regra, mock, request autenticado, `ServiceResponse` |
|
|
130
|
+
| Entities | `entities/<feature>.entities.ts` | `<Feature>.Model` / `.DTO` derivados de `Api.<Org>.<Api>.*` (fonte única) e tipos de UI que a API não tem |
|
|
131
|
+
| Domains | `domains/<feature>.domain.ts` | regra pura, limites, opções |
|
|
132
|
+
| Schemas | `schemas/<feature>.schemas.ts` | Zod do input de formulário, deep link e texto livre |
|
|
133
|
+
| Mappers | `mappers/<x>.mapper.ts` | tradução API ↔ UI |
|
|
134
|
+
| Hooks da feature | `hooks/<x>.store.hook.ts`, `hooks/use-<x>.hook.ts` | store de fluxo entre telas; hook reutilizado por 2+ telas |
|
|
135
|
+
| Components da feature | `components/<x>.component.tsx` | componente ou provider de domínio usado por 2+ telas |
|
|
136
|
+
| Mocks | `mocks/` | massa de dados do modo mock |
|
|
137
|
+
| Gateway (exceção) | `gateways/<x>.gateway.ts` | fonte fora do OpenAPI: `fetch` com timeout, resultado discriminado |
|
|
138
|
+
| Common | `src/common/*` | infra sem produto |
|
|
139
|
+
|
|
140
|
+
## Nomes
|
|
141
|
+
|
|
142
|
+
| Arquivo | Export / formato |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `<nome>.screen.tsx` | `<Nome>Screen`; props por `<Area>ScreenProps<"<Rota>">` |
|
|
145
|
+
| `<nome>.module.tsx` | `<Nome>Module`, registrado em `Module.<Secao>` |
|
|
146
|
+
| `<nome>.overview.module.tsx` | module principal da tela ou da aba de visão geral |
|
|
147
|
+
| `<nome>.component.tsx`, `<nome>.modal.tsx` | componente local; modal ou sheet local |
|
|
148
|
+
| `<nome>.step.tsx`, `<nome>.items.ts`, `<nome>.storage.ts` | etapa de fluxo multi-step; itens estáticos da tela (menu, opções); flag local não sensível |
|
|
149
|
+
| `use-<x>.hook.ts` | `use<X>` (controller: `use<X>Controller`) |
|
|
150
|
+
| `<x>.store.hook.ts` | `use<X>Store` |
|
|
151
|
+
| `<acao>.<feature>.service.ts` (feature no singular para item, plural para lista: `create.invoice`, `list.invoices`); algumas features usam `<acao>-<objeto>.service.ts` | `<nome em camelCase>Service`; na feature existente, siga a forma dela |
|
|
152
|
+
| `<feature>.entities.ts` | `namespace <Feature> { Model; DTO.<Acao>.{Input, ApiBody, Output} }` |
|
|
153
|
+
| `<feature>.schemas.ts` | `<feature>Schemas = { <acao>: z.object(...) }` + `namespace <Feature>Schema` |
|
|
154
|
+
| `<feature>.domain.ts` | constantes `UPPER_SNAKE` e funções puras |
|
|
155
|
+
| `<x>.mapper.ts` | `map<X>` (função pura) |
|
|
156
|
+
| `<x>.gateway.ts` | `fetch<X>Gateway`, tipo `<X>GatewayResult` |
|
|
157
|
+
| `<area>.navigator.tsx` | `<Area>Navigator` |
|
|
158
|
+
| `<nome>.<tipo>.util.ts` | utilitário em `common/utils/<tipo>/`, com `index.ts` |
|
|
159
|
+
| `*.unit.spec.ts` | teste ao lado do arquivo testado |
|
|
160
|
+
| evento de observabilidade | `<feature>.<acao>` (`invoices.create`) |
|
|
161
|
+
| `code` de erro | `<FEATURE>_<ACAO>_FAILED` ou o motivo de negócio (`INVOICE_REQUIRES_LINES`) |
|
|
162
|
+
|
|
163
|
+
## Caminho de uma ação
|
|
164
|
+
|
|
165
|
+
```txt
|
|
166
|
+
module ou controller (onPress)
|
|
167
|
+
-> <acao>.<feature>.service.ts
|
|
168
|
+
withServiceObservability("<feature>.<acao>", async () => {
|
|
169
|
+
regra de negócio -> serviceFailure("<CODE>", mensagem) (inline ou domains/)
|
|
170
|
+
modo mock? -> serviceSuccess(mock)
|
|
171
|
+
authedRequest((accessToken) => api.<org>.<api>.client.<operationId>({
|
|
172
|
+
baseUrl, cache, headers: Authorization, path, query, body }))
|
|
173
|
+
res.error -> serviceFailure("<FEATURE>_<ACAO>_FAILED", apiErrorMessage(res.error, fallback))
|
|
174
|
+
(ou o mapper de falha comum, quando a API devolve uma família de erro conhecida)
|
|
175
|
+
sucesso -> serviceSuccess(res.data.data) (envelope desembrulhado)
|
|
176
|
+
})
|
|
177
|
+
<- ServiceResponse
|
|
178
|
+
module ou controller
|
|
179
|
+
status "success" -> atualiza estado, navega, feedback de sucesso
|
|
180
|
+
status "error" -> useFeedbackHook.show({ message, mode: "error" }); fieldErrors no formulário
|
|
181
|
+
espera -> ação que trava a tela: useLoadingHook.show() e hide() no finally
|
|
182
|
+
ação local: estado submitting do próprio module
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Leitura de tela: `screen -> controller (efeito de foco) -> list service (dedupe em voo) -> estado -> Module.<Lista>`.
|
|
186
|
+
|
|
187
|
+
## Regras não negociáveis
|
|
188
|
+
|
|
189
|
+
1. **Screen fina.** Screen e module falam com services; `common/openapi` é importado só por services (da feature ou locais) e por entities (`import type`).
|
|
190
|
+
2. **Request pelo service.** O service chama `api.<org>.<api>.client.<operationId>` com `baseUrl` e `cache` de `api.<org>.<api>.settings`; rota autenticada vai dentro de `authedRequest`, que entrega o token e renova a sessão no 401; rota pública (login, cadastro, recuperação) chama o client sem token.
|
|
191
|
+
3. **Gateway é exceção.** `gateways/` serve só fonte fora do contrato OpenAPI: `fetch` com timeout, devolve `{ data } | { error }` e nunca lança. Operação que existe no contrato vai pelo client gerado.
|
|
192
|
+
4. **`ServiceResponse` sempre.** Todo service roda dentro de `withServiceObservability("<feature>.<acao>", ...)` e devolve `serviceSuccess` ou `serviceFailure`; a UI decide por `status`.
|
|
193
|
+
5. **Erro com `code` estável e mensagem útil.** `code` UPPER_SNAKE do app; mensagem da API (`apiErrorMessage(error, fallback)`) quando ela explica o motivo, texto do service quando não; status HTTP e corpo do erro ficam fora da tela (diagnóstico vai para o log). App com i18n segue o web: mensagem traduzida pelo `code`; a da API fica no log.
|
|
194
|
+
6. **Input tipado e validado.** O service recebe o DTO de `entities`; formulário, deep link e texto livre passam pelo Zod de `schemas/` antes de chegar ao service; regra que barra a chamada é `serviceFailure` antes do request.
|
|
195
|
+
7. **Mock dentro do service.** O ramo do modo mock vive só no service, depois das regras e antes do request; module e screen chamam o service igual nos dois modos.
|
|
196
|
+
8. **Sessão no `secureStorage`.** Token e usuário da sessão ficam só no `secureStorage` (wrapper de `expo-secure-store`) com chaves de `settings.storage.keys`; o header `Authorization` nasce no callback do `authedRequest`.
|
|
197
|
+
9. **Gate no root.** O root navigator segura o splash até o bootstrap resolver a sessão e escolhe `Public` ou `Private`; providers do fluxo privado envolvem só o `PrivateNavigator`.
|
|
198
|
+
10. **Params tipados e leves.** Toda rota tem ParamList em `navigation/types.ts`; params levam ids e dicas leves de exibição (título, thumbnail, estado vindo da lista); objeto inteiro e dado sensível ficam fora, e o resto vem do service.
|
|
199
|
+
11. **Env fail-closed.** `EXPO_PUBLIC_*` vai inteiro para o bundle: só valor não secreto. Schema Zod em `common/env/app.env.ts`, `assertAppEnv()` no `AppProviders`, leitura no uso (getter ou função), https fora de local/dev.
|
|
200
|
+
12. **Log com redaction.** Logs passam pelo `logger` de `common/observability`; `console.*` e devtools ficam sob `__DEV__` (exceção: o diagnóstico de env inválido, que precisa aparecer em release).
|
|
201
|
+
13. **Tela completa.** Safe Area, teclado e estados loading/error/empty em toda tela; lista longa em `FlatList`/`FlashList` (pelo wrapper de `common/components`, quando existe).
|
|
202
|
+
14. **Gerado não se edita.** `common/openapi/<org>/<api>/setup/` sai do `openapi-ts.config.ts`; operação faltando, contrato errado ou `operationId` ruim se corrige na API (`arch-api`) e se regenera com `openapi:generate`.
|
|
203
|
+
15. **Boundaries.** `common` não importa `screens` nem `modules`; `domains` e `entities` não importam React/RN.
|
|
204
|
+
16. **Gate de entrega.** Cada script da tabela "Comandos" roda no seu gatilho; o inventário `screens.md` do app reflete as telas do diff; mudança nativa declara iOS e Android testados ou "não testado".
|
|
205
|
+
|
|
206
|
+
## Protocolo: nova tela
|
|
207
|
+
|
|
208
|
+
```txt
|
|
209
|
+
1. Leia o inventário screens.md do app e um grupo vizinho de complexidade parecida.
|
|
210
|
+
Pronto: você cita o path do grupo que serve de modelo.
|
|
211
|
+
2. Escolha o grupo: rotas que dividem modules, controller ou service local (lista e detalhe, etapas de um
|
|
212
|
+
fluxo, variantes) ficam no mesmo grupo; assunto sem nada em comum ganha (<screen>)/ novo.
|
|
213
|
+
Variante da mesma rota por param: "Variantes da mesma rota" em references/screens.md.
|
|
214
|
+
Pronto: o grupo escolhido e o motivo cabem em uma frase.
|
|
215
|
+
3. Registre a rota: ParamList em types.ts, Stack.Screen ou tab no navigator, linking.ts se há deep link.
|
|
216
|
+
Pronto: navigation.navigate("<Rota>", params) compila sem cast.
|
|
217
|
+
4. Crie <nome>.screen.tsx fina, modules.tsx e os modules por seção.
|
|
218
|
+
Pronto: a screen só importa Module, o controller (se houver), tipos de navegação e componentes de layout.
|
|
219
|
+
5. Controller hook e service local: só pelo critério de [references/screens.md](references/screens.md).
|
|
220
|
+
6. Service: reutilize ou crie em modules/<feature> (protocolo abaixo).
|
|
221
|
+
7. Feche a regra 13 com a mecânica de references/screens.md.
|
|
222
|
+
Pronto: cada fluxo assíncrono da tela tem carregando, erro, vazio e conteúdo.
|
|
223
|
+
8. Atualize o inventário screens.md do app. Pronto: a linha da tela aponta grupo, rota e status.
|
|
224
|
+
9. Rode typecheck. Pronto: verde, ou o erro relatado com arquivo e linha.
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Protocolo: novo service
|
|
228
|
+
|
|
229
|
+
```txt
|
|
230
|
+
1. Confirme a operação no client gerado (sdk.gen.ts / operationId); faltando ou errada: regra 14.
|
|
231
|
+
Pronto: api.<org>.<api>.client.<operationId> existe e tipa.
|
|
232
|
+
2. DTO em entities: <Feature>.DTO.<Acao>.{Input, ApiBody, Output} derivados de Api.<Org>.<Api>.*.
|
|
233
|
+
Pronto: o service não declara tipo de request ou response à mão.
|
|
234
|
+
3. Regra pura reutilizável ou que merece teste: domains/ (+ .unit.spec.ts).
|
|
235
|
+
4. Escreva <acao>.<feature>.service.ts pelo template de [references/services.md](references/services.md):
|
|
236
|
+
observabilidade -> regra -> mock -> authedRequest -> erro -> sucesso.
|
|
237
|
+
5. Leitura que várias telas ou efeitos disparam ao mesmo tempo: dedupe em voo (references/services.md).
|
|
238
|
+
6. Integre no module ou controller pelo "Caminho de uma ação". Pronto: os dois status têm tratamento na UI.
|
|
239
|
+
7. Spec da regra pura quando há runner. Pronto: typecheck verde e spec verde (ou lacuna declarada).
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Comandos
|
|
243
|
+
|
|
244
|
+
Descubra os nomes reais no `package.json`; em monorepo, `<pm> --filter <app> <script>`.
|
|
245
|
+
|
|
246
|
+
| Script | Gatilho |
|
|
247
|
+
|---|---|
|
|
248
|
+
| `typecheck` | toda mudança TypeScript (gate mínimo) |
|
|
249
|
+
| `openapi:generate` | operação nova ou alterada na API |
|
|
250
|
+
| `openapi:check` | contrato ou client gerado mudou |
|
|
251
|
+
| `deps:check`, `doctor` | dependência, plugin Expo, `app.json`/`app.config`, asset nativo |
|
|
252
|
+
| `start`, `prebuild`, builds de plataforma | validar em device ou simulador |
|
|
253
|
+
| lint e test | os do monorepo, quando existirem |
|
|
254
|
+
|
|
255
|
+
## Anti-padrões
|
|
256
|
+
|
|
257
|
+
Sintomas que aparecem no diff, cada um com a regra que resolve.
|
|
258
|
+
|
|
259
|
+
- `import { api }` ou `fetch(` num module, screen ou controller → regras 1 e 3.
|
|
260
|
+
- `if (environment.DATA_SOURCE_MODE` fora de um service → regra 7 (o app de referência tem uma exceção num module de upload; não copie).
|
|
261
|
+
- Service devolvendo `res.data` com o envelope quando o consumidor espera o item → "Caminho de uma ação".
|
|
262
|
+
- `throw` num service para erro esperado da API → regra 4.
|
|
263
|
+
- Objeto inteiro do recurso passado em `navigation.navigate` → regra 10.
|
|
264
|
+
- `environment.<X>` ou `appEnv.<X>` lido no escopo do módulo → regra 11.
|
|
265
|
+
- `ScrollView` com `.map` numa lista que cresce → regra 13.
|
|
266
|
+
- Arquivo em `common/openapi/<org>/<api>/setup/` alterado sem `openapi:generate` → regra 14.
|
|
267
|
+
- Componente, hook ou service duplicado entre grupos de tela → colocação.
|
|
268
|
+
|
|
269
|
+
## Critério de entrega
|
|
270
|
+
|
|
271
|
+
```txt
|
|
272
|
+
[ ] inventário feito; o padrão do vizinho foi seguido (path citado)
|
|
273
|
+
[ ] regras 1-16 conferidas no diff, cada uma ok ou "não se aplica"
|
|
274
|
+
[ ] protocolo da mudança (tela, service) completo até o último passo
|
|
275
|
+
[ ] scripts rodados no gatilho, com resultado, ou lacuna declarada
|
|
276
|
+
[ ] divergências do repo em relação a esta skill registradas, com migração incremental
|
|
277
|
+
[ ] resumo com paths concretos e o que não foi testado em device
|
|
278
|
+
```
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# Navegação, sessão e env
|
|
2
|
+
|
|
3
|
+
Abra ao mexer em boot, navigator, rota, params, deep link, login/logout/refresh, `secureStorage` ou env. As regras (gate no root, params leves, sessão no `secureStorage`, env fail-closed) estão no SKILL.md; aqui fica a mecânica.
|
|
4
|
+
|
|
5
|
+
## Ordem de boot
|
|
6
|
+
|
|
7
|
+
```txt
|
|
8
|
+
index.ts
|
|
9
|
+
1. import do handler global de erro (primeira linha: erro JS não tratado vira alerta nativo, não tela branca)
|
|
10
|
+
2. polyfills que outros módulos exigem no import (ex.: crypto.getRandomValues)
|
|
11
|
+
3. devtools só em __DEV__ (require dentro do if)
|
|
12
|
+
4. registerRootComponent(App)
|
|
13
|
+
App.tsx
|
|
14
|
+
GestureHandlerRootView > KeyboardProvider > ErrorBoundary > AppProviders >
|
|
15
|
+
RootNavigator + StatusBar + overlays globais (loader, toast)
|
|
16
|
+
AppProviders (common/providers/app.providers.tsx)
|
|
17
|
+
assertAppEnv() -> SafeAreaProvider -> AppQueryProvider -> children
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
`assertAppEnv()` roda dentro do `ErrorBoundary`: env inválido mostra a mensagem do schema na tela em vez de derrubar o app no import.
|
|
21
|
+
|
|
22
|
+
## Root navigator
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
// src/navigation/root.navigator.tsx
|
|
26
|
+
const Stack = createNativeStackNavigator<RootStackParamList>();
|
|
27
|
+
|
|
28
|
+
export function RootNavigator() {
|
|
29
|
+
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
|
|
30
|
+
const isLoading = useAuthStore((state) => state.isLoading);
|
|
31
|
+
const { minSplashElapsed, onboardingSeen } = useAppBootstrap();
|
|
32
|
+
|
|
33
|
+
// Splash até sessão, onboarding e tempo mínimo resolverem: o Public não mostra um segundo splash.
|
|
34
|
+
if (isLoading || !minSplashElapsed || onboardingSeen === null) return <SplashScreen />;
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<NavigationContainer linking={linking}>
|
|
38
|
+
<Stack.Navigator screenOptions={{ headerShown: false }}>
|
|
39
|
+
{!isAuthenticated ? (
|
|
40
|
+
<Stack.Screen name="Public" navigationKey="public">
|
|
41
|
+
{() => <PublicNavigator onboardingSeen={onboardingSeen} />}
|
|
42
|
+
</Stack.Screen>
|
|
43
|
+
) : (
|
|
44
|
+
<Stack.Screen name="Private" navigationKey="private">
|
|
45
|
+
{() => (
|
|
46
|
+
<RealtimeProvider>
|
|
47
|
+
<DomainProvider>{/* provider de domínio de uma feature */}
|
|
48
|
+
<PrivateNavigator />
|
|
49
|
+
<NotificationBanner />
|
|
50
|
+
</DomainProvider>
|
|
51
|
+
</RealtimeProvider>
|
|
52
|
+
)}
|
|
53
|
+
</Stack.Screen>
|
|
54
|
+
)}
|
|
55
|
+
</Stack.Navigator>
|
|
56
|
+
</NavigationContainer>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- A troca `Public` ↔ `Private` acontece pelo `isAuthenticated` da auth store; `navigationKey` descarta o histórico da outra pilha.
|
|
62
|
+
- Providers do fluxo privado (regra 9): realtime, provider de domínio de uma feature (`modules/<feature>/components/`), e overlays que dependem deles (banner de notificação) como irmãos do `PrivateNavigator`.
|
|
63
|
+
- Telas e services chamam `setUser`/`clearUser` da store; nenhuma tela navega para `Public`/`Private` à mão.
|
|
64
|
+
|
|
65
|
+
## Navigators
|
|
66
|
+
|
|
67
|
+
```txt
|
|
68
|
+
public.navigator.tsx pilha anônima (onboarding, login, cadastro, recuperação)
|
|
69
|
+
private.navigator.tsx pilha privada: rota de bottom tabs (`MainTabs`) + rotas empilhadas sobre as tabs
|
|
70
|
+
settings.navigator.tsx pilha aninhada de configurações, registrada como uma rota do private
|
|
71
|
+
tab-routes.tsx TAB_ROUTES (name, component, label, onPress?), ícones e a rota central
|
|
72
|
+
components/ peças só da navegação (tab bar customizada, header de pilha)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
- Rota nova em pilha: `<Stack.Screen name="<Rota>" component={<Nome>Screen} options={{ animation }} />` no navigator da área; a screen vem do grupo em `src/screens/(…)/(<screen>)/`.
|
|
76
|
+
- Opções visuais (`animation`, `presentation: "fullScreenModal"`, `contentStyle`) ficam no `Stack.Screen`; cor por token do tema.
|
|
77
|
+
- Tab nova: entrada em `TAB_ROUTES` e chave em `PrivateTabParamList`.
|
|
78
|
+
- Overlay de todas as telas privadas que não depende de provider do root (assistente flutuante) é irmão do `Stack.Navigator` dentro do `PrivateNavigator`.
|
|
79
|
+
|
|
80
|
+
## `types.ts`
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
export type PublicStackParamList = { Welcome: undefined; SignIn: undefined };
|
|
84
|
+
export type PrivateTabParamList = { Dashboard: undefined; CustomersTab: undefined };
|
|
85
|
+
export type PrivateStackParamList = {
|
|
86
|
+
MainTabs: NavigatorScreenParams<PrivateTabParamList>;
|
|
87
|
+
Invoices: { customer?: string } | undefined;
|
|
88
|
+
InvoiceDetail: {
|
|
89
|
+
invoice: string;
|
|
90
|
+
kind?: "draft" | "issued";
|
|
91
|
+
/** dica de exibição enquanto o detalhe carrega */
|
|
92
|
+
title?: string;
|
|
93
|
+
};
|
|
94
|
+
CreateInvoice: { customer?: string } | undefined;
|
|
95
|
+
};
|
|
96
|
+
export type RootStackParamList = { Public: undefined; Private: undefined };
|
|
97
|
+
|
|
98
|
+
export type PrivateScreenProps<T extends keyof PrivateStackParamList> = NativeStackScreenProps<PrivateStackParamList, T>;
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
- Uma ParamList por pilha ou tab; um alias `<Area>ScreenProps<T>` por ParamList.
|
|
102
|
+
- Param opcional: `{ ... } | undefined`, para a rota abrir sem params (menus que navegam por `keyof` exigem).
|
|
103
|
+
- A dica de exibição pinta a tela antes do service responder; quando o service responde, vale o dado dele.
|
|
104
|
+
- Param que decide permissão ou estado sensível é conferido pela API na chamada do service.
|
|
105
|
+
|
|
106
|
+
## `linking.ts`
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
function resolvePrefixes(): string[] {
|
|
110
|
+
try {
|
|
111
|
+
return [Linking.createURL("/")];
|
|
112
|
+
} catch {
|
|
113
|
+
return []; // build sem scheme: deep link desligado, app sobe
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const linking: LinkingOptions<RootStackParamList> = {
|
|
118
|
+
prefixes: resolvePrefixes(),
|
|
119
|
+
config: {
|
|
120
|
+
screens: {
|
|
121
|
+
Public: { screens: { SignIn: "sign-in" } },
|
|
122
|
+
Private: {
|
|
123
|
+
screens: {
|
|
124
|
+
MainTabs: { screens: { Dashboard: "dashboard" } },
|
|
125
|
+
InvoiceDetail: "invoices/:invoice",
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
- `createURL` roda no import e lança em build sem scheme; o `try/catch` mantém o app de pé.
|
|
134
|
+
- A árvore de `config.screens` espelha a árvore de navigators; rota com deep link entra aqui no mesmo PR.
|
|
135
|
+
- Deep link para rota privada com usuário anônimo cai no `Public`; param de deep link chega como string (regra 6).
|
|
136
|
+
|
|
137
|
+
## Bootstrap de sessão
|
|
138
|
+
|
|
139
|
+
`src/navigation/use-app-bootstrap.hook.ts` roda uma vez no root:
|
|
140
|
+
|
|
141
|
+
```txt
|
|
142
|
+
1. tempo mínimo de splash (timer)
|
|
143
|
+
2. sincronizações com fallback embutido (ex.: manifesto de marca): fire-and-forget, o splash não espera
|
|
144
|
+
3. flags locais (onboarding visto) lidas do storage
|
|
145
|
+
4. sessão:
|
|
146
|
+
a. identidade de instalação: instalação nova ou restore em outro aparelho descarta a sessão herdada do keychain
|
|
147
|
+
b. access token no secureStorage e não expirado -> restaura o usuário salvo
|
|
148
|
+
c. senão -> refresh; autenticado -> restaura o usuário; falhou -> clearUser()
|
|
149
|
+
d. qualquer exceção -> clearUser(); finally -> setLoading(false)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Usuário salvo é JSON no `secureStorage`; parse inválido vira `clearUser()`.
|
|
153
|
+
|
|
154
|
+
## Auth store e `secureStorage`
|
|
155
|
+
|
|
156
|
+
- Auth store (`modules/auth/hooks/auth.store.hook.ts`): `user`, `isAuthenticated`, `isLoading`, `setUser`, `clearUser`, `setLoading`. Guarda o usuário para a UI; tokens ficam fora dela.
|
|
157
|
+
- `common/storage/secure-storage.ts` envolve `expo-secure-store` com cache em memória:
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
const cache = new Map<string, string | null>();
|
|
161
|
+
|
|
162
|
+
export const secureStorage = {
|
|
163
|
+
get: async (key: string) => {
|
|
164
|
+
const cached = cache.get(key);
|
|
165
|
+
if (cached !== undefined) return cached; // `null` lido também é memorizado
|
|
166
|
+
const value = await SecureStore.getItemAsync(key);
|
|
167
|
+
cache.set(key, value);
|
|
168
|
+
return value;
|
|
169
|
+
},
|
|
170
|
+
set: async (key: string, value: string) => {
|
|
171
|
+
try { await SecureStore.setItemAsync(key, value); cache.set(key, value); }
|
|
172
|
+
catch (error) { cache.delete(key); throw error; } // write nativo falhou: o cache não afirma nada
|
|
173
|
+
},
|
|
174
|
+
delete: async (key: string) => {
|
|
175
|
+
try { await SecureStore.deleteItemAsync(key); cache.set(key, null); }
|
|
176
|
+
catch (error) { cache.delete(key); throw error; }
|
|
177
|
+
},
|
|
178
|
+
clearCache: () => cache.clear(),
|
|
179
|
+
};
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
- O cache evita idas ao keychain a cada request; ele só fica coerente porque todo write da aplicação passa pelo wrapper. Módulo que precisa de chave própria fora do wrapper usa chaves próprias e documenta.
|
|
183
|
+
- Chaves em `common/settings/storage/keys.storage.ts`, expostas como `settings.storage.keys.<chave>` (`accessToken`, `refreshToken`, `user`, ...).
|
|
184
|
+
- Flag não sensível (onboarding visto, preferência local) pode usar storage comum, num `<nome>.storage.ts` ao lado de quem lê.
|
|
185
|
+
|
|
186
|
+
## Refresh, 401 e logout
|
|
187
|
+
|
|
188
|
+
- O retry no 401 é do `authedRequest` ([services.md](services.md)); ele e o bootstrap usam o mesmo service de refresh (`refresh-session.auth.service.ts`).
|
|
189
|
+
- Renovação única: requests concorrentes com 401 esperam a mesma promise de refresh (requisito e lacuna em [services.md](services.md)).
|
|
190
|
+
- Refresh que falha limpa tokens e usuário; a auth store volta a anônimo e o root troca para `Public`.
|
|
191
|
+
- Logout resiliente: tenta revogar na API sem bloquear; apaga `accessToken`, `refreshToken` e `user` do `secureStorage`; `clearUser()`.
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
export async function logoutAuthService(): Promise<ServiceResponse<null>> {
|
|
195
|
+
return withServiceObservability("auth.logout", async () => {
|
|
196
|
+
const refreshToken = await secureStorage.get(settings.storage.keys.refreshToken);
|
|
197
|
+
if (refreshToken) {
|
|
198
|
+
// Revogação na API pelo client gerado; API fora do ar não bloqueia o logout local.
|
|
199
|
+
await api.<org>.<api>.client.<logoutOperationId>({ baseUrl: api.<org>.<api>.settings.url, body: { refreshToken } })
|
|
200
|
+
.catch(() => undefined);
|
|
201
|
+
}
|
|
202
|
+
await Promise.all([
|
|
203
|
+
secureStorage.delete(settings.storage.keys.accessToken),
|
|
204
|
+
secureStorage.delete(settings.storage.keys.refreshToken),
|
|
205
|
+
secureStorage.delete(settings.storage.keys.user),
|
|
206
|
+
]);
|
|
207
|
+
useAuthStore.getState().clearUser();
|
|
208
|
+
return serviceSuccess(null);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Env
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
// src/common/env/app.env.ts
|
|
217
|
+
const schema = z
|
|
218
|
+
.object({
|
|
219
|
+
API_URL: z.string().url(),
|
|
220
|
+
APP_ENV: z.enum(["local", "development", "staging", "production"]).default("production"),
|
|
221
|
+
OBSERVABILITY_ENABLED: z.enum(["true", "false"]).default("true").transform((v) => v === "true"),
|
|
222
|
+
DATA_SOURCE_MODE: z.enum(["MOCK", "REAL"]).default("REAL"),
|
|
223
|
+
})
|
|
224
|
+
.refine((env) => env.APP_ENV === "local" || env.APP_ENV === "development" || env.API_URL.startsWith("https://"), {
|
|
225
|
+
path: ["API_URL"],
|
|
226
|
+
message: "API_URL deve usar https:// quando APP_ENV é staging ou production.",
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const raw = {
|
|
230
|
+
API_URL: process.env.EXPO_PUBLIC_API_URL, // acesso estático: é o que o bundler inlina
|
|
231
|
+
APP_ENV: process.env.EXPO_PUBLIC_APP_ENV,
|
|
232
|
+
OBSERVABILITY_ENABLED: process.env.EXPO_PUBLIC_OBSERVABILITY_ENABLED,
|
|
233
|
+
DATA_SOURCE_MODE: process.env.EXPO_PUBLIC_DATA_SOURCE_MODE,
|
|
234
|
+
};
|
|
235
|
+
const result = schema.safeParse(raw);
|
|
236
|
+
|
|
237
|
+
export const error: Error | null = result.success ? null : new Error(/* issues + valores recebidos */);
|
|
238
|
+
|
|
239
|
+
// Válido: objeto plano. Inválido: Proxy que lança com a mensagem acima em qualquer leitura.
|
|
240
|
+
export const environment = result.success
|
|
241
|
+
? result.data
|
|
242
|
+
: (new Proxy({}, { get: (_t, key) => { throw new Error(`[environment] leitura de "${String(key)}" com env inválido.\n\n${error?.message}`); } }) as z.infer<typeof schema>);
|
|
243
|
+
|
|
244
|
+
export function assertAppEnv(): void {
|
|
245
|
+
if (error) throw error;
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
- A mensagem de erro lista as issues e os valores crus recebidos no bundle, para o diagnóstico apontar a variável ausente (o default de `APP_ENV` esconde um `undefined`).
|
|
250
|
+
- Além do getter da regra 11: código que roda em todo service e lê uma flag (`withServiceObservability`) envolve a leitura em `try/catch` e assume o valor seguro.
|
|
251
|
+
- `common/env/index.ts` reexporta o mesmo objeto validado (na referência, como `appEnv`); siga o nome que o vizinho importa.
|
|
252
|
+
- Variável nova: schema, `raw`, `.env.example` e configuração de build no mesmo PR.
|
|
253
|
+
- Segredo que o app precisa vem da API ou da sessão em tempo de execução.
|
|
254
|
+
|
|
255
|
+
## expo-router
|
|
256
|
+
|
|
257
|
+
Repo com expo-router: siga o repo. `app/_layout.tsx` faz o papel do root navigator (providers, splash até o bootstrap resolver, redirecionamento por sessão), o `_layout.tsx` dos grupos `(public)`/`(private)` faz o papel dos navigators de área, e cada arquivo de rota em `app/` delega para a screen em `src/screens/(…)/(<screen>)/`. Params chegam por `useLocalSearchParams` como string e passam pelo schema antes de uso sensível. `screens`, `modules`, `common`, services e sessão seguem esta skill sem mudança.
|