- ApiClient: IllegalArgumentException de URL vira Network (banner, sem crash);
DTOs com listas nullable (servidor podia enviar categories/tags: null)
- SyncEngine: mutex global serializa runs (logs BEGIN/END); resolveConflict
idempotente (não duplica cópia de conflito); registra rekey:<local>→<server>
em meta para o editor aberto sobreviver à troca de id do create
- ViewModel: resolve(id) em save/delete; createLocal enfileira create único
- Outbox: índice único (noteId, op); DB v2 com fallbackToDestructiveMigration
- proguard: regras corretas de kotlinx.serialization (Companion.serializer)
- README: seção 'Testado em' com os cenários de emulador executados
- Room (notes/outbox/categories/meta): edição 100% local, fila FIFO de mutações
- SyncEngine bidirecional: push (create/update com base_hash/delete, rekey de id
local→servidor) + pull (categorias, notas divergentes, exclusões remotas)
- Conflito 409: servidor vence a nota + texto local preservado em cópia
"Título (conflito)" enfileirada como create — nada perdido, sem relógios
- WorkManager: periódico 1h (rede + backoff exponencial), runNow único REPLACE
em gatilhos (pós-save, rede restabelecida, boot, config, botão manual)
- OkHttp com timeout/retry em IOException; kotlinx.serialization; DataStore
- UI Compose: tela Servidor (URL+token+teste), Lista com banner de status
(Offline/Sincronizando/Sincronizado/Erro/401 + pendências + conflitos),
Editor com autosave por ociosidade (10 s) e categorias multi-seleção
- Build validado: assembleDebug (AGP 8.7.3/Kotlin 2.0.21/SDK 35, JDK 21) —
app-debug.apk 17,6 MB; gradle-wrapper.jar fora do commit (regenerável com
`gradle wrapper`, documentado no README)