Files
MeowSpool/mobile/agent.md
T
Felipe 416e13893c feat: implement authentication flow and session management
- Update RootLayout to handle authentication state and redirect users based on their auth status.
- Create an Index component to redirect unauthenticated users to the login page.
- Modify ApiAuthRepository to fetch user session data using access and refresh tokens.
- Introduce a new container file to manage use case instances for authentication and filament operations.
- Enhance authStore to validate tokens and fetch user data from the API.
- Add babel-plugin-module-resolver for improved module imports.
- Update package.json scripts for running the app on Android and iOS.
- Add expo-camera dependency for camera functionalities.
- Update tsconfig.json to include infrastructure path mapping.
2026-03-14 13:22:17 -03:00

18 KiB

MeowSpool Mobile — Agent Guide

Visão Geral

Aplicação mobile do MeowSpool construída com React Native + Expo (SDK ~51), usando expo-router para navegação baseada em sistema de arquivos. A arquitetura espelha o backend Rust: Clean Code / Hexagonal, com camadas bem delimitadas do domínio até a apresentação.

O app é offline-first: todos os dados são persistidos localmente em SQLite (expo-sqlite + SQLCipher) e sincronizados com o backend Rust em background usando estratégia last-write-wins via campo updatedAt.


Stack

Componente Biblioteca Observação
Framework expo ~51 Managed Workflow
Navigation expo-router ~3 File-system routing
Linguagem TypeScript 5.x strict mode
Node Version 22 LTS Gerenciado via mise (.mise.toml)
Java Version 17 Obrigatório para React Native 0.81 (Java 24 quebra CMake)
UI React Native + @expo/vector-icons Ionicons
Forms react-hook-form + zod Validação em runtime
Estado global zustand Stores em src/store/
HTTP axios Interceptor Bearer + refresh automático
DB local expo-sqlite + SQLCipher WAL mode, foreign keys
Auth persistência expo-secure-store JWT cifrado no keychain
Safe Area react-native-safe-area-context
Gesture Handler react-native-gesture-handler
Animations react-native-reanimated
QR Code render react-native-qrcode-svg Render de QR Code em tela
Câmera / Scanner expo-camera ~17 ML Kit barcode scan; requer development build

Arquitetura de Camadas

src/
├── domain/          ← Entidades puras (sem dependências externas)
├── ports/           ← Interfaces/contratos (traits equivalentes ao Rust)
├── application/     ← Use Cases (orquestram domínio + ports)
├── adapters/
│   ├── remote/      ← Implementações HTTP (Axios → API Rust)
│   └── local/       ← Implementações SQLite (expo-sqlite)
├── store/           ← Estado em memória (Zustand) — cache das queries
├── shared/          ← Design tokens, constantes, utilitários
└── presentation/
    └── components/  ← Componentes UI reutilizáveis

Regra de dependência

presentation → store → application → ports ← adapters
                                  ↑
                               domain

Nenhuma camada interna importa de camadas externas. Os adapters implementam os ports.


Estrutura de Arquivos

mobile/
├── .mise.toml                            ← node 22 LTS, java 17
├── app.json                              ← Expo config, scheme "meowspool"
├── babel.config.js                       ← module-resolver + reanimated
├── tsconfig.json                         ← aliases @domain, @ports, @application,
│                                           @adapters, @presentation, @store, @shared
├── index.js                              ← expo-router entry point
├── package.json
│
├── app/                                  ← expo-router file-system routes
│   ├── _layout.tsx                       ← Root layout (carrega sessão)
│   ├── (auth)/
│   │   ├── _layout.tsx                   ← Redireciona se já autenticado
│   │   ├── login.tsx                     ← G3-0
│   │   ├── register.tsx                  ← IX-0
│   │   ├── forgot-password.tsx           ← L2-0
│   │   ├── verify-email.tsx              ← RU-0
│   │   ├── reset-password.tsx            ← 117-0
│   │   └── password-reset-done.tsx       ← 135-0
│   └── (app)/
│       ├── _layout.tsx                   ← Stack autenticado
│       ├── (tabs)/
│       │   ├── _layout.tsx               ← Bottom tabs: Início|Estoque|[+]|Config|Perfil
│       │   ├── home.tsx                  ← 1-0   Dashboard
│       │   ├── inventory.tsx             ← CW-0  Inventário
│       │   ├── add.tsx                   ← FAB → redireciona para /inventory/new
│       │   ├── config.tsx                ← M4-0  Presets de Carretéis
│       │   └── profile.tsx               ← 1LQ-0 Perfil
│       ├── inventory/
│       │   ├── new.tsx                   ← 2X-0  Cadastro de Filamento
│       │   ├── filters.tsx               ← 17H-0 Filtros (bottom sheet)
│       │   ├── [id].tsx                  ← 6L-0  Detalhe do Filamento
│       │   └── [id]/
│       │       └── edit.tsx              ← 13O-0 Editar Filamento
│       ├── config/
│       │   └── presets/
│       │       ├── new.tsx               ← QM-0  Novo Preset
│       │       └── [id]/
│       │           └── edit.tsx          ← 1KD-0 Editar Preset
│       ├── scanner.tsx                   ← Scanner de QR Code (expo-camera ML Kit)
│       └── filaments/
│           └── [id]/
│               ├── qrcode.tsx            ← Ver QR Code (react-native-qrcode-svg)
│               └── label.tsx             ← Exportar Etiqueta SVG
│
└── src/
    ├── domain/
    │   ├── Filament.ts                   ← interface Filament, calcNetWeight
    │   ├── SpoolPreset.ts                ← interface SpoolPreset, isUserOwnedPreset
    │   ├── User.ts                       ← interface User, AuthSession, DTOs
    │   └── Dashboard.ts                  ← DashboardData, MaterialSummary, etc.
    ├── ports/
    │   ├── FilamentRepository.ts         ← interface IFilamentRepository
    │   ├── SpoolPresetRepository.ts      ← interface ISpoolPresetRepository
    │   └── AuthRepository.ts             ← interface IAuthRepository
    ├── application/
    │   ├── filament/
    │   │   ├── CreateFilamentUseCase.ts
    │   │   ├── UpdateFilamentUseCase.ts
    │   │   ├── ListFilamentsUseCase.ts
    │   │   └── DeleteFilamentUseCase.ts
    │   ├── preset/
    │   │   └── PresetUseCases.ts         ← List, Create, Update, Delete
    │   └── auth/
    │       └── AuthUseCases.ts           ← Login, Register, Google, Logout, etc.
    ├── adapters/
    │   ├── remote/
    │   │   ├── httpClient.ts             ← Axios + interceptors JWT + refresh
    │   │   ├── ApiAuthRepository.ts      ← /api/v1/auth/*
    │   │   ├── ApiFilamentRepository.ts  ← /api/v1/filaments/*
    │   │   └── ApiSpoolPresetRepository.ts ← /api/v1/spool-presets/*
    │   └── local/
    │       ├── database.ts               ← init SQLite, WAL, foreign keys, tabelas
    │       ├── LocalFilamentRepository.ts
    │       └── LocalSpoolPresetRepository.ts
    ├── store/
    │   ├── authStore.ts                  ← Zustand: sessão, SecureStore
    │   ├── filamentStore.ts              ← Zustand: lista + filtros em memória
    │   └── presetStore.ts                ← Zustand: systemPresets + userPresets
    ├── shared/
    │   ├── theme.ts                      ← Design tokens (cores, tipografia, espaçamento)
    │   ├── constants.ts                  ← API_BASE_URL, SecureStore keys, MATERIALS
    │   └── utils/
    │       └── filament.ts               ← calcFilamentPercentage, formatWeight, etc.
    └── presentation/
        └── components/
            ├── ui/
            │   ├── Button.tsx            ← primary, secondary, ghost, danger
            │   ├── Input.tsx             ← label, leftIcon, rightLabel, isPassword, error
            │   ├── Card.tsx              ← container surface com borda
            │   └── Badge.tsx             ← badge dinâmico de estoque
            ├── layout/
            │   ├── Screen.tsx            ← SafeArea + scroll + keyboardAvoiding
            │   └── Header.tsx            ← título centralizado, back, slot direito
            └── filament/
                ├── StockBar.tsx          ← barra de progresso + badge numérico
                ├── ColorSwatch.tsx       ← quadrado/círculo com cor hex
                └── FilamentCard.tsx      ← card de lista

Design Tokens

Arquivo: src/shared/theme.ts

Paleta (siamês)

Token Valor Uso
bgBase #1E1B18 Fundo geral
bgSurface #2A2622 Cards, containers
bgHover #332F2B Hover/pressed em cards
textPrimary #F5EEDC Títulos, peso líquido
textSecondary #C9C1B0 Descrições, labels
accent #38BCC2 Botões de ação, elementos ativos
accentMuted #38BCC226 Background de badges accent (10%)
stockLow #FF6B6B ≤ 15% — vermelho
stockMedium #FF9F43 ≤ 35% — laranja
stockOk #38BCC2 > 35% — accent
border #3D3830 Bordas sutis
error #FF6B6B Mensagens de erro

Tipografia

  • UI: Inter
  • Mono (slugs, hex, URLs): JetBrains Mono
  • App é dark-mode exclusivo — nenhum suporte a light mode

Navegação (expo-router)

Estrutura de grupos

(auth)  ← sem autenticação; redireciona para (app) se sessão válida
(app)   ← com autenticação; redireciona para (auth) se sem sessão
  (tabs) ← bottom tabs fixos
  • Scheme: meowspool://
  • Filamento: meowspool://filament/<id>/(app)/inventory/<id> (singular, alinhado com backend)
  • O scanner (scanner.tsx) faz match via /meowspool:\/\/filament\/([^/]+)/ e navega para /(app)/inventory/<id>
  • O QR Code de cada filamento exibe meowspool://filament/<id> usando react-native-qrcode-svg

Estratégia Offline-First

  1. Escrita: toda mutação persiste primeiro no SQLite (synced = 0) e enfileira em sync_queue.
  2. Leitura: lê sempre do SQLite; a API é usada apenas para sync.
  3. Sync: background job consome sync_queue e envia para o backend Rust.
  4. Conflito: last-write-wins via updatedAt (ISO 8601). O backend é a fonte de verdade em conflitos.
  5. Auth offline: token armazenado no expo-secure-store; refresh automático via interceptor Axios.

Schema SQLite

-- filaments
id TEXT PRIMARY KEY, user_id TEXT, material TEXT, brand TEXT, model TEXT,
color_hex TEXT, spool_preset_id TEXT, total_weight_g REAL, net_weight_g REAL,
temp_hotend_c REAL, temp_bed_c REAL, flow_factor_pct REAL, notes TEXT,
synced INTEGER DEFAULT 0, updated_at TEXT, created_at TEXT

-- spool_presets
id TEXT PRIMARY KEY, user_id TEXT, name TEXT, spool_weight_g REAL,
is_system INTEGER DEFAULT 0, synced INTEGER DEFAULT 0, created_at TEXT

-- sync_queue
id INTEGER PRIMARY KEY AUTOINCREMENT, entity_type TEXT, entity_id TEXT,
operation TEXT, payload TEXT, created_at TEXT

Convenções de Código

Nomenclatura

  • Componentes React: PascalCase (FilamentCard.tsx)
  • Hooks: camelCase com prefixo use (useFilamentStore)
  • Use Cases: PascalCase com sufixo UseCase (CreateFilamentUseCase)
  • Repositórios: PascalCase com prefixo de implementação (ApiFilamentRepository, LocalFilamentRepository)
  • Stores: camelCase (filamentStore.ts), exportados como useXxxStore

Imports

Sempre usar aliases em vez de caminhos relativos:

import { Filament } from '@domain/Filament';
import { useFilamentStore } from '@store/filamentStore';
import { colors } from '@shared/theme';
import { Button } from '@presentation/components/ui/Button';

Componentes de tela

  • Toda tela usa SafeAreaView com backgroundColor: colors.bgBase
  • Header manual (não usa o header do expo-router): headerShown: false
  • CTA fixo: View com padding: spacing[5] + borderTopWidth: 1 + borderTopColor: colors.border
  • ScrollViews com showsVerticalScrollIndicator={false} e keyboardShouldPersistTaps="handled"

Formulários

  • react-hook-form + zodResolver para todos os formulários
  • Validações no schema Zod (nunca inline)
  • isLoading local no componente durante submit
  • Erros de API exibidos via Alert.alert

API do Backend (rotas relevantes)

Base: EXPO_PUBLIC_API_URL (padrão: http://localhost:3000/api/v1)

Auth

Método Rota Descrição
POST /auth/login Login email/senha
POST /auth/register Cadastro
POST /auth/google OAuth Google
POST /auth/refresh Refresh token
POST /auth/logout Logout
POST /auth/forgot-password Solicitar reset de senha
POST /auth/reset-password Confirmar reset com token
POST /auth/verify-email Verificar e-mail com código

Filaments

Método Rota Descrição
GET /filaments Listar (com filtros)
POST /filaments Criar
GET /filaments/:id Detalhe
PATCH /filaments/:id Atualizar
DELETE /filaments/:id Excluir
GET /filaments/:id/qrcode QR Code SVG
GET /filaments/:id/label Etiqueta SVG

Spool Presets

Método Rota Descrição
GET /spool-presets Listar (sistema + usuário)
POST /spool-presets Criar preset do usuário
PATCH /spool-presets/:id Atualizar preset do usuário
DELETE /spool-presets/:id Excluir preset do usuário

Dashboard

Método Rota Descrição
GET /dashboard Dados agregados do dashboard

Próximos Passos (integrações pendentes)

  • Instanciar e injetar repositórios concretos nos Use Cases (DI container simples ou Context)
  • Implementar sync background com sync_queue SQLite → API
  • Integrar react-native-qrcode-svg para render real do QR Code
  • Scanner de QR Code com expo-camera v17 (ML Kit) → scanner.tsx
  • Gerar SVG de etiqueta (integração com /filaments/:id/label do backend)
  • Expo Notifications para alertas de estoque baixo
  • Google OAuth com expo-auth-session
  • Testes de integração com Jest + Testing Library

Executando o Projeto

# Instalar dependências (na pasta mobile/)
mise install   # garante Node 22 e Java 17
npm install

# Iniciar servidor de desenvolvimento (Expo Go — sem câmera ML Kit)
npx expo start

# Build nativo Android (necessário para expo-camera ML Kit barcode scan)
mise exec -- npx expo run:android

# Build nativo iOS
mise exec -- npx expo run:ios

Variável de ambiente: crie mobile/.env com:

EXPO_PUBLIC_API_URL=http://localhost:3000/api/v1

Atenção ao build Android: o projeto usa namespace "com.meowspool" no build.gradle mas o código fonte fica em com.meowspool.app. Por isso MainActivity.kt e MainApplication.kt importam explicitamente com.meowspool.R e com.meowspool.BuildConfig, e o AndroidManifest.xml usa nomes totalmente qualificados (com.meowspool.app.MainApplication, com.meowspool.app.MainActivity). Não usar nomes relativos (.MainApplication) no manifest — eles resolvem para com.meowspool.* (sem .app), causando ClassNotFoundException em runtime.