


Alterar uma aplicação de mídia grande para desktop sem travar a UI nem quebrar o desfazer
Para uma aplicação grande para desktop que lida com arquivos e mídia: a IA tira o trabalho pesado da thread de UI, cobre cancelamentos e casos extremos, mantém o desfazer exato e informa só os testes que realmente rodou.
Para usar estas regras de IA
## Seguir todo o caminho de chamadas e dar um único dono a cada estado
- Entenda a funcionalidade responsável e as abstrações existentes antes de editar, e rastreie todo o caminho de chamadas pela UI, automação, desfazer, persistência e trabalho em segundo plano.
- Para mudanças não triviais, identifique a fonte da verdade, o dono do estado, o domínio de isolamento, os invariantes e o comportamento de falha e cancelamento antes de escrever código.
- Desenhe a arquitetura antes dos detalhes de implementação, e prefira uma mudança coerente a remendos espalhados por camadas não relacionadas.
- Mantenha um único dono autoritativo para cada parte do estado mutável; o estado derivado é calculado ou mantido por um cache com um contrato de invalidação explícito.
- Reutilize as operações de domínio existentes; não adicione uma segunda implementação só porque quem chama é uma pré-visualização, uma view, um controller, uma ferramenta de automação ou um teste.
- Coloque o código junto da funcionalidade que é dona dele, reserve utilitários compartilhados para infraestrutura usada por várias funcionalidades independentes, e nomeie tipos e arquivos de forma que a propriedade fique clara.
## Manter comentários curtos e remover código morto
- Mantenha os comentários no mínimo: escreva um apenas quando o motivo, o invariante, a restrição de segurança ou a solução alternativa não forem óbvios, e limite-o a uma linha curta.
- Não narre o código, não repita nomes em comentários, não descreva o patch atual, não deixe rastros de remoção nem mantenha código comentado.
- Prefira nomes precisos, tipos pequenos e operações extraídas em vez de comentários explicativos.
- Um cálculo ou uma regra de negócio tem uma única fonte da verdade; nunca a copie para outro arquivo ou superfície.
- Remova código morto, estado não utilizado, caminhos de compatibilidade obsoletos e diagnósticos temporários antes de finalizar.
- Não adicione código de compatibilidade para plataformas, versões ou configurações que o projeto não suporta.
## Tirar o trabalho pesado da thread de UI e reconferir o estado após cada await
- Trate a thread de UI como um recurso escasso: ela pode manter estado de UI e coordenação leve, mas não deve realizar operações de arquivo, decodificação, inferência de modelo, processamento de imagem, indexação, exportação, chamadas bloqueantes ou transformações grandes.
- Isolamento não é desempenho: marcar um trabalho como assíncrono ou envolvê-lo em uma task não o tira da thread atual. Siga o executor real.
- Torne as trocas de thread explícitas; capture o mínimo de entrada imutável necessária antes de sair de uma thread, retorne um valor e só então aplique-o depois de confirmar que ainda está atualizado.
- Depois de todo ponto de suspensão, assuma que o estado compartilhado mudou: revalide identidade, geração, seleção e pré-condições antes de confirmar um resultado.
- Prefira concorrência estruturada; uma task não estruturada precisa de um dono claro, um handle armazenado quando o cancelamento importa, e um encerramento bem definido.
- O cancelamento é cooperativo: laços longos devem verificá-lo em pontos úteis e nunca confirmar resultados parciais ou desatualizados.
- Limite o trabalho paralelo pelo recurso escasso real, elimine requisições idênticas já em andamento, adquira e libere gates em escopos correspondentes, e retome cada continuation ou callback exatamente uma vez.
- Nunca bloqueie a thread de UI com esperas síncronas, semáforos, locks ou polling para trabalho assíncrono.
## Fazer o trabalho com arquivos fora da thread de UI e substituí-los de forma atômica
- Toda operação de sistema de arquivos roda fora da thread de UI: leituras, escritas, verificações de existência e metadados, enumeração, cópia, movimentação, substituição, exclusão e criação de diretórios.
- Assuma que qualquer volume pode ser lento, removível, modificado externamente ou baseado em rede; um tamanho pequeno ou um acesso anterior bem-sucedido não torna o acesso síncrono seguro.
- Capture uma cópia dos dados do modelo antes do trabalho com arquivos, e não altere estado observável a partir do executor de trabalho com arquivos.
- Prepare a saída completa fora do destino ativo, monte a substituição no destino e então instale-a atomicamente; direcione as escritas ativas por um único coordenador em vez de escrever diretamente.
- Serialize operações que atingem o mesmo arquivo ou destino para que salvar, importar, gerar, criar miniaturas, excluir, exportar e fechar nunca entrem em disputa de concorrência (race condition).
- Use caminhos temporários únicos e limpe-os em caso de sucesso, falha e cancelamento; nunca exclua ou substitua um destino até que a substituição completa esteja pronta.
- Exponha as falhas de arquivo solicitadas pelo usuário; não as esconda atrás de erros ignorados, resultados vazios ou respostas que aparentam sucesso.
## Medir antes de afirmar uma melhora e manter leves os caminhos frequentes
- Identifique caminhos sensíveis a desempenho, como renderização, reprodução, tratamento de entrada, atualizações de view, importação, indexação, salvamento, restauração e exportação, e trate-os como críticos (hot paths).
- Não realize acesso ao sistema de arquivos, logging, serialização, configuração de modelo ou decodificador, nem outra configuração bloqueante dentro de caminhos executados por item, por frame ou por interação repetida.
- Meça antes de afirmar uma melhoria: faça profiling ou benchmark da mesma carga de trabalho antes e depois da mudança.
- Corrija a complexidade algorítmica e o trabalho desnecessário antes de ajustes de baixo nível; fique atento a varreduras aninhadas, ordenações repetidas, mutação por cópia dentro de laços, alocações intermediárias e trocas repetidas de thread.
- Agrupe e combine mutações em lote, descarregando o estado pendente antes de snapshots, desfazer, exportação, fechamento e leituras que precisam retornar dados atuais.
- Carregue e hidrate os dados de forma preguiçosa (lazy), e armazene em cache trabalho caro e reutilizável apenas com uma chave explícita, capacidade, regra de invalidação e política para resultados desatualizados.
- Reutilize objetos caros sempre que o ciclo de vida permitir e invalide-os em mudanças relevantes de configuração; evite que estados de alta frequência, como progresso e medidores, invalidem views inteiras; limite a memória retida.
## Listar casos extremos e de falha antes de implementar
- Trabalhar apenas no caminho feliz (happy path) não é suficiente: antes de implementar, liste os casos aplicáveis de limite, ciclo de vida, concorrência e falha, e decida qual camada é responsável por cada um.
- Valide entradas vazias, nulas, zero, negativas, máximas, com overflow, não finitas, malformadas, duplicadas, ausentes, desatualizadas e não suportadas, conforme aplicável.
- Valide antes de operações aritméticas, indexação ou conversão numérica; nunca conte com um catch posterior para absorver uma armadilha aritmética, e defina arredondamento e limitação (clamping) exatos em vez de limitar silenciosamente.
- Cubra operações que não fazem nada (no-op) e operações repetidas: elas devem reportar com precisão e não devem criar mutações, entradas de histórico, trabalho duplicado ou sucesso enganoso.
- Considere o cancelamento antes do início, durante cada fase e após o trabalho terminar mas antes da confirmação, e considere conclusões desatualizadas após mudanças de seleção, alvo ou configuração.
- Considere os caminhos de encerramento: fechar, sair, suspender, despertar, desativação, mudanças de dispositivo e salvar como enquanto o trabalho está em andamento.
- Preserve os invariantes em todo caminho de falha: um sucesso parcial deve ser retomável com segurança e reportado como tal, ou revertido.
## Fazer cada ação do usuário ser desfeita em um único passo
- Direcione as ações do usuário e as ações automatizadas pelas mesmas operações de mutação de domínio e pelo mesmo histórico compartilhado.
- Uma intenção coerente do usuário produz uma única ação desfazível; não exponha subetapas internas como entradas separadas no histórico, a menos que cada uma tenha significado independente.
- Valide argumentos e pré-condições antes de abrir um grupo de histórico; operações que falharam, foram canceladas, recusadas ou que não mudaram nada não devem criar entradas vazias.
- O trabalho aninhado deve se fundir na ação externa sem fechar grupos pertencentes à plataforma ou a outro subsistema.
- Desfazer deve restaurar o estado exato, sem arredondamento cumulativo, desvio de estado derivado, relacionamentos órfãos ou seleção desatualizada.
- Quando uma mudança afeta o histórico, teste o intercalamento entre edições do usuário, edições automatizadas, agrupamento de eventos da plataforma e requisições concorrentes.
## Projetar ferramentas de automação a partir do que o usuário quer fazer
- Projete ferramentas a partir da intenção do usuário, não a partir de APIs internas, operações de armazenamento ou limites de serviço; defina o resultado, os critérios de sucesso, os avisos, a falha, o cancelamento, a idempotência e a semântica de desfazer antes do schema.
- Uma ferramenta executa uma ação coerente; uma chamada normalmente completa um fluxo de trabalho atômico, compreensível e desfazível. Não crie uma ferramenta genérica com modos não relacionados, e não force quem a chama a encadear ferramentas de baixo nível para um trabalho que o próprio sistema pode fazer com segurança.
- Expresse os parâmetros em conceitos de domínio voltados ao usuário e oculte o layout de armazenamento e os detalhes de implementação; use identificadores de entidade estáveis para automação, tratando índices posicionais e rótulos apenas como contexto, não como identidade durável.
- Trate todo argumento como não confiável: exija tipos exatos, números finitos, limites explícitos e identificadores válidos; resolva e valide a requisição completa antes de mutar, e aplique mudanças em múltiplas entidades de forma atômica.
- Reutilize as mesmas operações de domínio usadas pela UI, e não duplique a lógica de posicionamento, vinculação ou validação.
- Retorne recibos estruturados descrevendo o que mudou, identificadores estáveis, estado explícito de no-op, avisos e erros acionáveis; nunca retorne uma resposta que aparente sucesso quando o resultado foi ajustado ou não foi alcançado, e nunca limite, redirecione ou use um fallback silenciosamente, a menos que o contrato prometa e reporte isso.
- Ferramentas de longa duração devem expor um job durável ou um resultado final que quem chama possa inspecionar; mantenha os contratos voltados à máquina estáveis e separados do texto localizado; as descrições devem explicar quando, por que e como uma ferramenta interage com as outras.
## Manter o estado da UI na thread de UI e preservar o comportamento nativo da plataforma
- Mantenha o estado observável da UI na thread de UI e deixe que resultados de segundo plano cruzem a fronteira como valores imutáveis.
- Restrinja a observação à menor view que precisa de um valor; estados de alta frequência, como progresso, hover e reprodução, não devem invalidar árvores de views não relacionadas.
- Não inicie efeitos colaterais persistentes a partir do corpo de renderização; use tasks ou controllers cientes do ciclo de vida, com cancelamento e encerramento explícitos, e mantenha os corpos de renderização rápidos e livres de efeitos colaterais.
- Preserve o comportamento nativo da plataforma para foco, teclado, tecla escape, menus, restauração de janela, desfazer, estado de arrastar (drag) e confirmação de fechamento.
- Contratos de delegate e de completion handler devem se completar exatamente uma vez em todo caminho de sucesso, falha, cancelamento e alvo ausente.
- Não assuma que um callback de framework ou de conclusão chega na thread de UI, a menos que a API garanta isso.
## Processar mídia sem bloquear e manter exatos os valores de tempo e quadro
- Use carregamento assíncrono de propriedades para assets externos, e não chame acessores síncronos obsoletos que possam bloquear a thread de quem chama.
- Mantenha os valores em seu domínio exato, como inteiros precisos de tempo, frame ou unidade, pelo maior tempo possível, e converta para ponto flutuante apenas em fronteiras explícitas de UI ou de formato externo.
- Defina e preserve a semântica de origem, como timing, escala, transformações, cor, alfa, layout de canais e metadados, a menos que a funcionalidade os altere explicitamente.
- Reutilize readers, contextos de renderização e pipelines sempre que o ciclo de vida permitir, e não os reconstrua durante interação contínua, a menos que a invalidação exija isso.
- Limite decodificadores, readers, exportações, inferências e extrações concorrentes, e propague o cancelamento por todo laço de decodificação, renderização e exportação, verificando entre os blocos (chunks) quando uma etapa subjacente não pode ser cancelada.
- Teste com faixas ausentes, contêineres incomuns, duração zero ou indefinida, entradas rotacionadas ou com alfa, taxas fora do padrão e cancelamento.
## Usar tokens de design em vez de valores de estilo fixos no código
- Estilize tudo por meio do sistema compartilhado de design tokens; nunca fixe diretamente no código (hardcode) espaçamentos, tamanhos, pesos de fonte, raios, larguras de borda, opacidade, cores, sombras ou durações de animação.
- Se um token necessário não existir, adicione-o ao sistema de tokens primeiro, em vez de fixar um valor diretamente no código.
- A busca de recursos deve funcionar na aplicação empacotada, em execuções locais e em testes; use a abstração compartilhada de busca de recursos em vez de sondagens específicas de cada funcionalidade, e trate cada layout de bundle como uma configuração distinta a ser verificada.
- Não use flags exclusivas de debug para selecionar um caminho de recurso ou comportamento fundamentalmente diferente, a menos que a diferença seja intencional e testada.
- Ao adicionar um recurso empacotado, atualize o manifesto do pacote, os scripts de build e os testes, verifique o layout final empacotado, e mantenha o código opcional controlado por feature flag compilável tanto com quanto sem a flag.
## Dar a cada operação um fim visível e logs úteis
- Toda operação iniciada pelo usuário ou por automação deve chegar a um estado observável de sucesso, falha, recusa ou cancelamento.
- Não ignore um erro em uma fronteira de interação quando a falha muda o resultado; converta-o em estado de UI, erro de ferramenta, falha de job ou log acionável.
- Trabalho do tipo "fire-and-forget" só é permitido quando é explicitamente de melhor esforço; ele não deve ser dono de persistência obrigatória, e suas falhas devem poder ser ignoradas com segurança ou registradas.
- Registre falhas com a operação, identificadores estáveis e a fase do ciclo de vida; nunca grave segredos, credenciais ou entradas brutas do usuário em logs.
- Preserve diagnósticos suficientes para distinguir cancelamento, falha de validação, recurso indisponível, falha de framework e violação de invariante interno.
- Use assertions apenas para invariantes de programação, não para entradas de usuário recuperáveis, arquivos externos, disputas de ciclo de vida (races) ou requisições de automação.
## Escrever testes pequenos e determinísticos que rodem em paralelo
- Testes unitários devem ser concisos e focados em comportamento: um teste estabelece um invariante ou uma regressão com apenas a configuração necessária para deixá-lo claro, usando nomes descritivos e sem comentários que narrem os passos.
- Use testes parametrizados para matrizes de casos-limite e builders de fixtures compartilhados em vez de duplicar a configuração entre suítes.
- Teste pela menor costura (seam) estável que comprove o comportamento, e evite acoplamento a detalhes privados ou a contagens incidentais de chamadas.
- Toda correção de bug ganha um teste de regressão que falha para o defeito original quando é viável, e cobre caminhos negativos: validação, no-op, falha parcial, cancelamento, conclusão desatualizada e limpeza.
- Testes de concorrência devem se coordenar de forma determinística com gates, continuations ou hooks injetados, e não devem depender de sleeps, sorte de timing ou ordem de agendamento.
- Os testes rodam em paralelo: não compartilhe estado global mutável, nomes temporários fixos, portas ou diretórios; use diretórios temporários únicos e limpe-os depois, e mantenha credenciais reais, serviços externos e arquivos reais de usuário fora dos testes unitários.
## Revisar a correção primeiro e informar exatamente quais testes rodaram
- Revise a correção antes do estilo: rastreie o comportamento alterado por quem chama, o estado compartilhado, o trabalho em segundo plano, a persistência, o desfazer, a UI e as superfícies de automação.
- Priorize achados que causam perda de dados, corrupção, crashes, travamentos, race conditions, exposição de segurança, resultados incorretos, desfazer quebrado ou sucesso enganoso, e só depois desempenho, manutenibilidade e acabamento.
- Procure bloqueio da thread de UI, erros de concorrência como estado confirmado após uma suspensão sem revalidação, tasks sem dono, cancelamento ausente, fan-out sem limite, gates desbalanceados e callbacks retomados duas vezes, além de lógica de domínio duplicada, caches inseguros e aritmética de limite não validada; restrinja a revisão à mudança e ao que ela afeta diretamente.
- Verifique as afirmações: rode o build e os testes apropriados para a mudança, exercite os caminhos de cancelamento e de ciclo de vida para mudanças de concorrência, e meça mudanças de desempenho sob uma carga de trabalho representativa.
- Reporte exatamente o que foi e o que não foi executado; não afirme verificação completa de build, teste ou runtime quando apenas um subconjunto foi concluído.
- Mantenha os commits focados, com um prefixo de categoria estreito e preciso e sem limpezas não relacionadas, e descreva uma mudança pelo seu resumo, abordagem, principais decisões de design e evidências de teste, em vez da sequência de edições feitas.
