Buscar/Manter uma biblioteca de UI multiplataforma sem quebrar suas camadas de pacotes

Manter uma biblioteca de UI multiplataforma sem quebrar suas camadas de pacotes

Para uma biblioteca de UI dividida em pacotes por camadas para várias plataformas: a IA preserva as camadas e as exportações públicas, cria adaptadores de provedores com uma estrutura de arquivos fixa e segue as regras de lançamento e revisão.

Para usar estas regras de IA
## Decidir em qual camada de pacotes fica cada parte do código Organize a base de código como uma pequena pilha de camadas, cada uma com uma única responsabilidade: primitivas reativas que executam hooks de framework como recursos sem interface; uma camada de vinculação que conecta essas primitivas ao sistema de estado do framework; um runtime central independente de framework que detém a lógica de domínio; e um pacote de distribuição por plataforma-alvo (web, nativo, terminal) que reexporta o núcleo e adiciona primitivas de interface específicas da plataforma. Um kit de componentes privado guarda o código-fonte canônico da interface, que é copiado para os projetos consumidores em vez de publicado como dependência de runtime. As ferramentas de build compartilhadas ficam em seu próprio pacote interno. Regras de posicionamento: - A lógica independente de framework pertence ao pacote central. Os runtimes acoplados à plataforma pertencem aos pacotes de distribuição. - O runtime acoplado ao framework fica em um subcaminho do pacote central, que toda distribuição reexporta, de modo que exista uma única implementação em vez de uma cópia por plataforma. - Os consumidores nunca instalam o pacote central diretamente; instalam exatamente um pacote de distribuição. - Os pacotes de integração que mapeiam um provedor externo para o runtime central formam sua própria camada e nunca bifurcam a lógica central. ## Apenas adicionar exportações públicas, nunca remover as já publicadas A superfície pública de todo pacote publicado é somente para adição. Quando um símbolo é movido, redirecione sua exportação para o novo arquivo; nunca exclua uma exportação que já foi lançada. Uma auditoria interna do repositório não consegue ver os consumidores externos, então remover até mesmo um tipo aparentemente não utilizado é uma mudança que quebra compatibilidade. Publique uma mudança real de comportamento como sua própria mudança deliberada, nunca disfarçada dentro de uma movimentação ou renomeação. Quando uma migração longa substitui uma implementação legada por uma nova arquitetura, o ponto de entrada do pacote reexporta ambos os caminhos durante o período de transição e continua sendo somente para adição. Durante essa migração: - Adicione novo comportamento na superfície canônica, nunca no caminho descontinuado. - Reutilize a primitiva central compartilhada em vez de escrever uma cópia dela por integração. - Prefira a reutilização a introduzir mais uma opção de exclusão. ## Criar um adaptador de provedor com a estrutura de arquivos padrão Um adaptador de integração mapeia um provedor externo para o runtime central por meio de um hook de entrada único, um conjunto de hooks de acesso e conversores puros. A regra geral que rege isso: o arquivo de runtime orquestra, os módulos puros convertem, o controlador (quando existe um) reduz, e os hooks apenas leem. - Escolha do runtime central. Construa sobre o runtime de armazenamento externo, quando as mensagens são derivadas de uma fonte que o provedor possui, ou sobre o runtime local mais um adaptador de modelo de chat, quando o provedor não mantém estado de thread. Envolva qualquer um deles no runtime multithread compartilhado quando listas de threads forem necessárias. Reutilize os núcleos de runtime já distribuídos; não crie manualmente um armazenador de estado de runtime de thread paralelo. - Exposição de estado. Exponha o estado do runtime aos hooks de acesso por meio da fábrica compartilhada de extras de runtime, não por uma marca de símbolo único e guarda feitos à mão. - Conjunto padrão de arquivos. Um único arquivo de entrada de runtime somente de orquestração, uma instância de extras de runtime, um arquivo de hooks de acesso e ação, um arquivo puro de conversor bidirecional de mensagens e um arquivo de tipos. Adicione um controlador de thread mais um redutor de estado puro quando o adaptador possui estado de thread, e um subcaminho de entrada separado de servidor ou node quando o protocolo é dono do formato do fio de dados. - Código exclusivo de servidor. Mantenha o código exclusivo de servidor e o SDK do provedor dentro do subcaminho de servidor ou node, fora do ponto de entrada padrão e fora do ponto de entrada nativo. - Testes. Coloque-os ao lado de cada módulo, cobrindo o conversor em ambas as direções, o redutor ou controlador, e cada hook de acesso. As escolhas conduzidas pelo provedor permanecem flexíveis: em qual primitiva central construir, wrapper fino versus acumulador versus controlador, transportes personalizados, quão rico é o suporte de humano no circuito, e até que profundidade vai o suporte a listas de threads. ## Igualar os recursos dos adaptadores e impedir que os conversores falhem Paridade entre runtimes. Mantenha as capacidades em paridade entre os adaptadores. Quando uma capacidade chega a um runtime, exponha-a em todo runtime onde o conceito se aplique, ou registre por escrito por que um deles diverge. Uma lacuna silenciosa de paridade se torna uma expectativa quebrada para quem trocar de adaptador. Conversores defensivos. Conversores e renderizadores de blocos de conteúdo nunca devem lançar exceção em campos indefinidos, campos ausentes ou capacidades de plataforma indisponíveis. Defenda-se contra payloads de provedores fora da especificação, e mantenha as APIs de plataforma exclusivas do navegador fora dos caminhos de código que também rodam em um servidor, em um renderizador de terminal ou em um alvo nativo. Padrões que nunca devem ser introduzidos: - Um armazenador de estado de runtime de thread paralelo que duplica os núcleos de runtime já distribuídos. - Um truque de notificação de mudança mais contador de versão para forçar nova renderização, usado para conectar estado não reativo à árvore de componentes. - Enxertar métodos em objetos por meio da criação de protótipo. - Aplicar monkeypatch em objetos pertencentes ao chamador. ## Compilar com a ferramenta de build compartilhada e incluir notas de versão Todo pacote publicável é compilado por meio da ferramenta de build compartilhada. Não adicione uma configuração de build por pacote nem recorra a um empacotador alternativo ou diretamente à CLI do compilador. Os mapas de exportação são somente para módulos e priorizam tipos, com as declarações de tipo listadas antes da condição padrão, e os pacotes se declaram livres de efeitos colaterais. - Um único diretório canônico de código-fonte de interface. Modelos e exemplos o referenciam por meio de mapeamentos de caminho do compilador e não carregam duplicatas idênticas byte a byte, exceto quando um kit inicial mínimo deliberadamente distribui sua própria cópia. Um script de sincronização mantém essa cópia idêntica byte a byte, e toda divergência intencional é declarada na lista de exceções do script. - As convenções de formato de diretório diferem conforme a intenção: diretórios paralelos quando toda primitiva existe em ambas as variantes, e sufixos de variante esparsos quando apenas alguns arquivos têm uma segunda variante, com o arquivo sem sufixo servindo como fonte padrão. - Execute novamente a tarefa de verificação do plugin do compilador ao atualizar a cadeia de ferramentas do compilador ou seus plugins. Um build bem-sucedido sozinho não prova que a cadeia de ferramentas continua íntegra. - Todo pacote que o pacote publicado importa em tempo de execução pertence às dependências de runtime, não às dependências de desenvolvimento, para que o empacotador o trate como externo. Colocá-lo no lugar errado embute o pacote e arrasta importações transitivas irresolúveis para os builds dos consumidores. - Uma entrada de componente instalável por cópia deve ser autossuficiente: enumere toda importação interna de componente e toda importação de folha de estilo como dependência de registro, para que o instalador nunca entregue um arquivo com uma importação irresolúvel. - Toda mudança em um pacote publicado carrega uma entrada de nota de lançamento. Use sempre um incremento de patch; incrementos menores e maiores exigem aprovação do mantenedor. Pacotes privados estão isentos. ## Não editar à mão a referência de API gerada Toda a árvore de referência de API gerada pertence ao seu script gerador, não a edições manuais. - Páginas que carregam o marcador de página inteiramente gerada são de propriedade total do gerador. Edições manuais dentro de um bloco gerado são destruídas na próxima execução; o texto duradouro pertence, em vez disso, aos comentários da documentação-fonte. - Conteúdo escrito manualmente que precisa sobreviver à regeneração vai apenas nos slots manuais ou de exemplo designados. - Uma página inteiramente mantida manualmente deve carregar o marcador de exclusão da geração automática logo após seu frontmatter. Esse marcador mantém a página listada na navegação da seção e a isenta da poda; sem ele, uma página inesperada não gerada é relatada como não gerenciada e desatualizada, e o modo estrito falha. - Os arquivos de metadados de navegação de seção e raiz nessa árvore também são gerados. Nunca os edite manualmente. - A integração contínua regenera em modo estrito e falha em qualquer divergência, então regenere localmente antes de enviar. ## Deixar o estilo com o linter e o formatador, e escrever poucos comentários Execute o linter do repositório e seu modo de correção automática para questões mecânicas. O formatador é dono da formatação; nunca formate manualmente, e nunca levante ressalvas de espaçamento ou formatação em revisão. Como os recursos reativos são construídos sobre hooks de framework, as regras de array de dependências e de ordem de hooks são impostas pelas regras nativas de hooks do linter. Para que essas regras consigam analisar um corpo de função, ela precisa ser nomeada de modo que as ferramentas do framework a reconheçam como um hook: extraia um recurso para uma função com prefixo de hook e passe-a para a fábrica de recursos, e dê aos auxiliares de criação de raiz uma expressão de função nomeada em vez de uma arrow function. Por padrão, zero comentários de código. Exclua qualquer comentário que apenas reafirme o código, registre seu histórico ou aponte para a solicitação de mudança atual. Mantenha um comentário apenas quando ele documentar um motivo que nenhum leitor futuro conseguiria recuperar somente a partir do código, como um invariante oculto, uma restrição não óbvia ou uma solução alternativa para algo a montante, e escreva-o como uma frase declarativa neutra. ## Colocar os testes ao lado de cada módulo e não usar snapshots Os testes ficam ao lado do módulo testado e o importam por caminho relativo, nunca pelo nome do pacote, de modo que um teste sempre exercite o código-fonte local em vez de uma saída de build já resolvida. - Cubra o conversor em ambas as direções, o redutor ou controlador, e cada hook de acesso em seu próprio arquivo de teste. - Construa mocks com o auxiliar de mock elevado (hoisted) do executor de testes, e sempre espalhe (spread) o módulo original dentro do mock, para que as exportações não alteradas mantenham seu comportamento real. - Não use asserções de snapshot; faça asserções sobre os valores específicos que importam, de modo que uma falha nomeie a regressão real. ## Enviar mudanças com um único objetivo, com descrição e reprodução - Uma preocupação por solicitação de mudança. Divida uma mudança grande ou com múltiplos propósitos em envios separados, para que cada um seja pequeno o suficiente para revisar, possa ser aprovado por seus próprios méritos e possa ser revertido de forma limpa quando causar regressão. - Escreva uma descrição real, não um espaço reservado gerado. Declare o que é a mudança, por que ela é necessária e como foi feita, para que um revisor tenha contexto suficiente sem precisar fazer engenharia reversa do diff. O título nomeia a mudança em uma linha; os trade-offs e divergências pertencem ao corpo do texto. - Justifique qualquer divergência da prática já existente no repositório. Se a base de código já resolve um problema de determinada forma, siga-a; alguém escolheu essa abordagem deliberadamente, então uma abordagem diferente precisa de um motivo por escrito, não de preferência pessoal. - Mantenha mudanças não relacionadas fora do diff. Exclusões incidentais, refatorações, reformatações e adições do tipo "já que estou aqui" enterram a mudança real e desaceleram a revisão. Abra um envio separado, ou uma issue quando o objetivo for apenas sinalizar algo. - Abra uma issue antes de uma mudança de funcionalidade não trivial. Um parágrafo permite que um mantenedor confirme que a direção é desejada antes que tempo significativo seja gasto. Correções triviais, como erros de digitação ou pequenas edições de documentação, não precisam de issue. - Anexe uma reprodução mínima. Todo relatório de defeito ou correção precisa de uma reprodução executável na versão exata. Se um mantenedor não conseguir reproduzir o comportamento relatado, o relatório é fechado. - Confirme que a correção resolve a causa raiz. Verifique se a falha relatada desaparece com a mudança e retorna sem ela, e explique o mecanismo. Uma mudança que mascara o sintoma trocando um valor, removendo um recurso ou ampliando um tipo é rejeitada. - Mantenha a documentação e a referência gerada em sincronia com o código na mesma submissão. Regenere a referência e atualize toda página cujas afirmações a mudança invalide. A automação deve falhar diante de divergência na documentação, em vez de deixar uma API desaparecer silenciosamente ou ser adiada para uma mudança posterior. ## Evitar mudanças que a revisão rejeita, como recriar recursos do framework - Reinventar o que o framework já oferece. Construa sobre as primitivas compartilhadas em vez de reimplementar estado de runtime, reatividade ou outra infraestrutura de apoio; um mecanismo paralelo se distancia do resto da base de código e custa a manutenção de todos. - Adicionar dependências pesadas ou não open-source de forma casual. Bibliotecas com licença comercial estão fora dos limites em um projeto open-source, e dependências pesadas ou novas etapas de build afetam todo contribuidor, portanto precisam de justificativa e aprovação do mantenedor. - Publicar uma nova aplicação de exemplo que duplica uma já existente. Examine primeiro os exemplos existentes; se algum já cobre a integração, um quase-duplicado não agrega nada e pertence a um repositório pessoal separado, a menos que um mantenedor tenha pedido por ele. - Corrigir um defeito introduzindo uma regressão na experiência do usuário. Desativar um recurso, remover uma animação ou ampliar uma interface para mascarar um defeito de renderização ou instabilidade não é uma correção aceitável, mesmo quando o relato original é real. - Adicionar verificações defensivas que a cadeia de ferramentas já impõe. Sob as configurações mais estritas do compilador, incluindo verificação exata de propriedades opcionais, as guardas de nulidade e de propriedade opcional são redundantes. - Substituir verificações obrigatórias para fazer o merge. Não force um merge até que a listagem de verificações mostre todas as linhas aprovadas ou explicitamente ignoradas; filtre as linhas aprovadas e ignoradas e confirme que nada restou, porque uma listagem truncada já ocultou uma verificação com falha uma vez. Quando uma verificação específica do repositório estiver falhando ou pendente, resolva-a em vez de substituí-la. ## Não sobrescrever mudanças de outros agentes na mesma árvore de trabalho Assuma que outros agentes automatizados e humanos estão editando a mesma árvore de trabalho ao mesmo tempo. Antes de editar, inspecione o estado atual da árvore de trabalho e evite sobrescrever mudanças que você não fez. Mantenha cada mudança rigorosamente delimitada, para que o trabalho concorrente possa ser separado de forma limpa. Respeite o fluxo de trabalho de controle de versão do projeto em vez de presumir que comandos brutos de controle de versão são apropriados. Quando o branch de trabalho indicar que o projeto é gerenciado por meio de uma ferramenta de fluxo de trabalho sobreposta ao controle de versão, não crie branches, prepare arquivos, faça commit ou reescreva o histórico com comandos diretos, a menos que explicitamente solicitado.