Memórias de um cara que passou tempo demais limpando a merda dos outros e pagando pelas más decisões deles, episódio #tantos que perdi a conta.

Copiar/colar histórico e estilos são recursos centrais no Ansel, e o que faz com que ele mereça (ou não) o título de “aplicativo de fluxo de trabalho”. Mas também está entre os mais difíceis de acertar internamente. Os usuários veem uma lista de edições, mas por baixo dos panos essas edições dependem da ordem do pipeline, das instâncias de módulos e das máscaras. Se duas imagens têm topologias de pipeline diferentes, copiar edições de forma ingênua pode produzir inconsistências.

Esta atualização torna a mesclagem de histórico robusta, consistente e transparente, após um trabalho tedioso de limpeza e simplificação de código. Ela também introduz um tratamento de erros claro quando uma mesclagem perfeita é matematicamente impossível.

Uma breve história de design ruim

Até o início de 2019, o Darktable era projetado em torno de um pipeline fixo: os módulos tinham uma ordenação que era decidida em tempo de compilação por um script Python , criado pelo fundador do Darktable, Johannes Hanika, em 2011 . A única coisa que você pode esperar do Jo é que ele acerte a matemática, e por isso esse script faz exatamente o que deveria ser feito em tais circunstâncias:

  • deixar os programadores declararem módulos,
  • deixá-los definir quais módulos devem vir antes de cada módulo, de um jeito preguiçoso e conveniente que permite apenas dizer “A deve vir antes de C”, “B deve vir antes de A”, “D deve vir antes de A”, etc.
  • transformar isso em um grafo dirigido , que é justamente o objeto matemático que junta todas essas restrições,
  • resolver o grafo dirigido com um dos algoritmos de ordenação topológica , que datam da década de 1960,
  • escrever o número de índice de cada módulo no pipeline, e pronto.

Infelizmente, esse pipeline fixo significava que mudar a ordenação relativa dos módulos em qualquer momento posterior quebraria históricos mais antigos. O que era péssimo, porque alguns erros de projeto foram cometidos com a posição da transformação de exibição e do módulo de curva base, que aconteciam cedo no pipeline e faziam as cores enlouquecerem em situações de HDR. Para corrigir isso, tive que criar o módulo filmic como outro módulo, a fim de poder colocá-lo no fim do pipeline. E todos os outros módulos referentes à tela teriam precisado ser duplicados na base de código para poder inseri-los onde necessário sem quebrar edições mais antigas (incluindo as minhas). Nem preciso dizer que o pipeline fixo já tinha dado o que tinha que dar, e eu era defensor dessa mudança.

Com um pipeline fixo, mesclar históricos, isto é, copiar/colar históricos entre imagens ou aplicar estilos a imagens (o que acaba sendo a mesma coisa), era fácil: era só uma questão de substituir os parâmetros e as máscaras dos módulos 1:1. Mesmo com módulos multi-instâncias, que haviam sido introduzidos por volta de 2014, não era tão ruim, porque todas as instâncias eram forçadas a ser consecutivas e numeradas relativamente umas às outras. Então, depois de criar as instâncias faltantes, em uma posição que era previsível e invariante (em ordem sequencial após a instância base), ainda era substituir os parâmetros e as máscaras dos módulos 1:1.

Mas o recurso tão necessário de reordenação do pipeline, em tempo de execução e pelos usuários, que chegou com o Darktable 3.0, foi feito da pior forma possível. O desenvolvedor que o implementou tratou a indexação com arrays de prioridades em ponto flutuante, que não é a estrutura de dados certa para o problema. Além disso, o código de gerenciamento de histórico não foi refatorado e simplificado antes de ser estendido, mas sim gambiarrado no lugar com o mínimo de mudanças, o que o tornou realmente complexo e obscuro. Pascal Obry, que havia aceitado a mudança em 2019, teve que reescrever todo o backend de reordenação do pipeline  em 2020, usando a estrutura de dados adequada para a tarefa em questão (listas encadeadas ), porque a anterior era frágil e impossível de manter.

Mas mesclar pipelines, que podem ter números diferentes de módulos, ordenados em lugares imprevisíveis e não invariantes, não pode ser feito de outra forma senão usando ordenação topológica em tempo de execução, porque não é mais um mero problema de históricos (ou seja, instantâneos dos parâmetros dos módulos), é um problema duplo que inclui os históricos, mas também a topologia do pipeline. Em outras palavras: precisamos resolver onde inserir as instâncias de módulos que existem no pipeline de origem mas não no de destino. E, no entanto, o script Python que fazia isso em tempo de compilação foi excluído em 2019, e o recurso nunca foi portado para C.

Então, o jeito como o Darktable, até hoje, lida com a mesclagem de pipelines é por meio de heurísticas gambiarradas a partir do paradigma do pipeline fixo e quebradas em geral, exceto nos casos bonitinhos que correspondem às condições dos pipelines de 2018 e anteriores:

  • se os seus pipelines de origem e destino têm o mesmo número de módulos ordenados da mesma forma, tudo corre bem.
  • se o seu pipeline de origem tem instâncias de módulos adicionais, comparado ao de destino, mas essas instâncias estão todas posicionadas imediatamente após a instância base, tudo ainda corre bem,
  • mas… se você tem instâncias adicionais, seja no pipeline de origem ou no de destino, e elas foram deslocadas no pipeline, então o comportamento é indeterminado, imprevisível, e eu venho copiando e colando histórico prendendo a respiração há mais de 5 anos,
  • além disso, há uma lista de módulos “cerca” que precisam de uma ordem relativa específica (como a calibração de cor que absolutamente precisa vir depois do perfil de cor de entrada), mas nenhuma forma de impô-la, nenhuma forma de corrigi-la quando ocorrem ordenações ruins, apenas mensagens de erro silenciosas no console.

Combine isso com máscaras raster, em que o módulo que reutiliza uma máscara raster absolutamente precisa ficar mais adiante no pipe do que o módulo que a produz, e em que, aliás, você pode querer copiar o produtor junto com o consumidor, ou pelo menos receber um aviso se não o fizer… e você tem a receita para a loucura.

Na prática, você vai precisar abrir cada imagem e verificar a pilha de módulos na sala escura, o que é dolorosamente lento.

Como o Darktable é definido por uma má gestão de prioridades, muita cosmética recebeu muito trabalho: ele vai deixar você editar suas fotos com gamepads de PlayStation, está prestes a ganhar recursos de mascaramento por aprendizado profundo, mas ainda consegue estragar o básico do básico, sem nenhuma melhoria em 6 anos apesar da aparente atividade no projeto.

O problema (TL;DR)

Anteriormente, a colagem de histórico tentava mesclar os módulos um a um conforme avançava. Isso funcionava em casos simples, mas se tornava não confiável quando:

  • a imagem de origem tinha uma ordem de pipeline diferente,
  • havia múltiplas instâncias do mesmo módulo,
  • ou máscaras e mesclagem estavam envolvidas.

Como a mesclagem não estava resolvendo o problema de ordenação inteiro, o resultado podia depender de muitas coisas. Em alguns casos, o pipeline final podia acabar inconsistente com a pilha de histórico.

A correção

Implementei um algoritmo de ordenação topológica  em C. Ou, mais precisamente, o ChatGPT implementou e eu verifiquei (mais sobre isso abaixo). 304 linhas de código, o que, para C, é muito pouco.

Agora tratamos a ordem do pipeline como um conjunto de restrições que deve ser resolvido globalmente, e não módulo a módulo. Na prática, isso significa:

  • Resolvemos primeiro a topologia do pipeline, e o histórico de revelação (parâmetros dos módulos) por último. Duas etapas claras que tornam possível o tratamento de erros.
  • A mesclagem calcula uma única ordem de pipeline válida que satisfaz tanto a origem quanto o destino sempre que possível, não importa quantas instâncias de módulos e como elas estão ordenadas.
  • Se as restrições forem incompatíveis, o Ansel pode explicar o conflito e perguntar qual ordenação preservar.
  • Se um conflito for insolúvel, o Ansel o reporta claramente em vez de produzir um pipeline quebrado ou instável, e dá a você opções para corrigi-lo.
  • Restrições também são aplicadas aos módulos que usam e consomem máscaras raster, e um aviso é emitido se um usuário de máscara raster for copiado sem o produtor.
  • Restrições codificadas de forma fixa são aplicadas aos módulos que exigem um ao outro antes, por razões técnicas (reconstrução de realces antes da interpolação cromática, perfil de cor de entrada antes da calibração de cor, etc.). Essas restrições são tratadas da mesma forma que as outras e resolvidas em conjunto (sem caso especial).

Em relação à segunda parte, o histórico propriamente dito, uma coisa precisa ser esclarecida aqui. O histórico do Darktable e do Ansel se parece muito com uma lista de desfazer/refazer de instantâneos dos parâmetros dos módulos: cada item do histórico está ligado a um módulo e representa seu estado interno de parâmetros e máscaras. Ao carregar o histórico nos nós do pipeline (nós sendo os filtros de pixel anexados aos “módulos” que você vê na interface gráfica), lemos o histórico de baixo para cima e copiamos cada item/instantâneo nos módulos, o que significa que instantâneos mais recentes sempre sobrescrevem os anteriores.

Ou seja, o histórico é ordenado pelo momento da modificação do usuário (de novo… pense em uma lista de desfazer/refazer de instantâneos), não pela ordem dos nós do pipeline. Mas, onde fica confuso é que os itens do histórico também armazenam a posição no pipeline de um módulo, o que significa que reordenar módulos deixa itens de histórico. Isso confundiu os desenvolvedores do Darktable, e muito mais os usuários. Então, vamos separar conceitualmente os dois, tanto em nossas mentes quanto no software.

A topologia (ordenação dos nós do pipeline) é independente do histórico. Diferentemente do Darktable, que gerencia tudo por meio dos itens do histórico (incluindo a ordem do pipeline), o que é ativamente prejudicial tanto em termos de compreensão quanto de complexidade do código (esses andam juntos de qualquer forma), nós resolvemos o pipeline como uma coleção autônoma de nós (módulos), e então reassociamos os nós ao seu último estágio de histórico. Levei alguns anos para enxergar através de toda a ofuscação avassaladora que acontecia neste software, enterrada em código copiado e colado, e ver a luz: uma vez abstraído, o problema é bem fácil.

Então, uma vez que a topologia é resolvida, ficamos com 3 modos de mesclagem de histórico:

  • replace: o histórico de origem substitui todo o de destino; módulos obrigatórios como a interpolação cromática para imagens RAW ainda podem ser adicionados por cima (para que copiar/colar seja seguro entre JPEG e RAW). Esse modo não leva a nenhuma ordenação topológica, é uma cópia direta do histórico e da ordem do pipeline.
  • append: o histórico de origem vai por cima do de destino, de modo que os itens de histórico que têm como alvo os mesmos módulos na origem e no destino são sobrescritos pela origem,
  • appstart: o histórico de origem vai por baixo do de destino, de modo que os itens de histórico que têm como alvo os mesmos módulos na origem e no destino são sobrescritos pelo destino.

A ordem do pipeline resolvida pela ordenação topológica é atualizada nos últimos itens do histórico, tanto no modo append quanto no appstart, o que significa que voltar no histórico também reverterá a ordenação topológica. Os históricos não são comprimidos de propósito, ao mesclar, para que os usuários mantenham a capacidade de reverter a mesclagem tanto usando os recursos de desfazer/refazer quanto voltando na caixa de ferramentas do histórico, na sala escura, antes do ponto de mesclagem.

Em resumo: a colagem de histórico agora é determinística, segura, não destrutiva, mesmo para edições complexas.

Como usar

Haverá um histórico de origem (que você copia) e um histórico de destino (onde você cola). O mesmo se aplicará aos estilos quando forem reimplementados; o histórico de origem será definido pelo estilo em vez de por outra imagem, e o resto será igual.

No menu global EditarModo de colagem de histórico, você pode escolher entre append, appstart ou replace. A configuração é global em todo o aplicativo. Ela determina qual histórico (origem ou destino) tem precedência ao sobrescrever módulos em comum.

Em EditarModo de colagem de nós, você pode ativar/desativar Copiar ordem dos módulos. Se estiver desativado, a ordem do pipeline do destino é mantida como está. Se estiver ativado, tentamos o nosso melhor para importar a ordem do pipeline de origem para o destino.

Como antes, no menu Editar, você tem as opções de copiar/colar tudo, ou apenas os módulos selecionados (por meio da janela modal). Atalhos globais estão disponíveis e são editáveis pelo usuário. Observe que copiar e colar históricos é explicitamente proibido na visualização de sala escura, mesmo a partir da tira de filme, porque é ambíguo determinar se você quer copiar entre miniaturas, da miniatura para a imagem principal ou o contrário. Na mesa de luz, você seleciona a origem, copia, seleciona o destino, cola, e tudo fica claro.

Agora, há uma suposição importante a ter em mente: módulos que têm o mesmo nome de instância (número da instância por padrão, ou nome definido pelo usuário) são considerados a mesma entidade nos históricos de destino e de origem. Assim, cada Exposição (céu) será mesclada com todo outro módulo Exposição (céu) (diferenciando maiúsculas de minúsculas), e deve haver apenas uma instância de Exposição (céu) nos históricos de destino e de origem. Anteriormente, o código usava números de instância, o que é mais frágil porque eles são impostos pelo software e incrementados na ordem de criação, o que não tem significado para os usuários.

Interface gráfica e tratamento de erros

A beleza da nova solução é que você não precisa abrir a sala escura para ver a bagunça que criou ao copiar e colar lixo; você pode revisá-la antes que qualquer dano seja causado às suas edições, na mesa de luz. Além disso, quando o solucionador falha em encontrar uma solução, o que acontece com restrições incompatíveis (A deve ser precedido por B, mas B deve ser precedido por A) ou ciclos (mais abaixo), ele é capaz de dizer o que falha, reportar isso, e ou solicitar a intervenção do usuário para corrigir, ou recorrer ao caminho mais sensato. Deixa eu te mostrar:

Ciclos triviais

A Exposição 1 vem antes da Exposição no histórico de origem, mas depois no histórico de destino. O conjunto de restrições acaba com Exposição → Exposição 1 → Exposição, o que é inviável. Aqui está o que acontece no Ansel:

image

Esses ciclos triviais que envolvem vizinhos imediatos são detectados antes da resolução, então não interrompem o fluxo de controle.

Ciclos não triviais

Esses ciclos não triviais envolvem vários módulos e não podem ser detectados antes de tentar resolver o grafo dirigido. Quando isso acontece:

image

Nesse caso, não há nada a fazer: tentaremos automaticamente de novo usando a ordem de destino, já que isso ocorre tipicamente ao tentar mesclar a ordem de origem no destino.

Máscaras raster esquecidas

Qualquer módulo que use uma máscara raster deve ser copiado junto com o seu módulo produtor de máscara, a menos que você planeje resolver isso você mesmo mais tarde. Só para o caso de ser um engano, se você tentar isso:

image

Você tem a chance de abortar a mesclagem agora mesmo se não era isso que você queria.

A interface do relatório de mesclagem (novo)

Básico

A caixa de diálogo do relatório foi projetada para responder a uma pergunta simples do usuário: “o que exatamente aconteceu com o meu pipeline?”

Ela mostra quatro pipelines lado a lado:

  1. Original (o pipeline de destino antes da mesclagem),
  2. Origem (a imagem de onde você copiou),
  3. Sobrescrita (onde as edições de origem substituíram as edições de destino),
  4. Destino (o pipeline final após a mesclagem).
image

Cada coluna lista as instâncias de módulos ativas, na ordem da interface gráfica. Esta visualização inclui marcadores adicionais:

  • Colchetes [nome] indicam módulos que foram recém-inseridos.
  • Um asterisco * indica módulos que usam máscaras.
  • Um rótulo em negrito indica módulos cuja posição relativa mudou entre origem e destino.
  • Setas de sobrescrita mostram onde o histórico de origem realmente substituiu as edições de destino (com →* quando as máscaras também foram sobrescritas).

Mimos

A coluna de destino é reordenável com arrastar e soltar, o que significa que, se você não estiver satisfeito com o resultado da ordenação topológica, você pode corrigi-lo você mesmo agora mesmo, antes mesmo de ser salvo no seu banco de dados e no XMP, e sem ter que abrir a sala escura. Isso permite que você ajuste o pipeline final manualmente antes de aceitá-lo, ou reverta tudo e não grave o histórico de volta.

Quando você reordena:

  • a ordem do pipeline é atualizada imediatamente,
  • as entradas do histórico são mantidas consistentes com a nova ordem,
  • e a visualização do relatório atualiza seus rótulos e marcadores de “movido” de acordo.

Isso é pensado como uma válvula de segurança: mesmo que a ordem calculada seja válida, você ainda tem uma forma simples de ajustá-la.

Por que isso importa

Isso melhora diretamente os fluxos de trabalho que envolvem edição em lote com pipelines complexos:

  • copiar edições entre imagens,
  • misturar origens/destinos RAW e JPEG,
  • e edições pesadas baseadas em multi-instâncias ou máscaras.

O objetivo é tornar a colagem de histórico previsível, mesmo quando os pipelines subjacentes diferem. Essa confiabilidade é especialmente importante para edições avançadas, em que pequenas diferenças de ordenação podem alterar os resultados.

Esta mudança não adiciona novos recursos chamativos — ela torna um dos recursos mais usados confiável. As mesclagens de histórico agora se comportam como os usuários esperam: resultados consistentes, relatórios claros e recuos seguros quando as restrições entram em conflito.

E eu não entendo por que, 6 anos depois, os caras do Funciona Na Minha Máquina® que estão travando o Darktable em câmera lenta não consideraram melhorar um recurso tão básico, porém tão crítico. Se isso não grita prioridades erradas, não sei o que gritaria.

O que tornou isso possível

Quero enfatizar aqui que toda essa reescrita foi possível porque eu quase inteiramente reescrevi o backend de tratamento de histórico no Ansel primeiro, já que era uma bagunça:

  1. Havia funções duplicadas por toda parte, que realizavam a mesma operação muitas vezes, mas escondidas em funções chamadoras/chamadas por todo o software, algumas induzindo I/O de sistema de arquivos (escrita de XMP) sem motivo, uma escrevendo o histórico de volta toda vez que abríamos a sala escura (o que estragava o carimbo de tempo da última alteração),
  2. Havia vários bloqueios de thread intercalados que basicamente tornavam qualquer alteração impossível sem travar as coisas em deadlock,
  3. Havia código de busca de histórico em SQLite3 entrelaçado com código C, muitas consultas SQL duplicadas, nenhuma delas thread-safe (porque o próprio SQLite3 não é thread-safe), então enterrei todo o código SQL dentro de uma interface C que trata a segurança de thread de forma centralizada, e agora todo o código C busca informações de histórico no banco de dados da biblioteca com uma única API, o que significa que sabemos que tudo que lê o histórico o lerá da mesma forma em todos os lugares do aplicativo,
  4. Algumas partes da leitura, inicialização e mesclagem do histórico eram feitas em SQL (aproveitando declarações JOIN, o que faz sentido, mas…), e outras eram feitas em C (porque as verificações de segurança dos módulos e a inicialização de predefinições são obviamente C). Isso levava a coisas estúpidas como reindexar manualmente os itens do histórico em C antes de salvar no banco de dados no meio de escritas transitórias (porque o SQLite3 não garante que os itens do histórico serão salvos no banco de dados na mesma ordem em que foram passados… é para isso que servem as chaves primárias). Então reescrevi a coisa toda em C, o que pode ser ligeiramente mais lento, mas garante a consistência dos dados: os históricos são tratados exatamente da mesma forma, seja ao carregá-los para mesclá-los, ao abrir a sala escura ou ao exportar uma imagem. Se houver um bug em algum lugar, ele estará em todos os lugares e o encontraremos mais cedo, além de que o corrigiremos em apenas um lugar.
  5. O código de gerenciamento de histórico estava entrelaçado também com código de interface gráfica, mas ele também pode rodar a partir do ansel-cli (sem interface gráfica), então isso levava a muitas heurísticas verificando se tínhamos ou não uma interface gráfica, em muitos lugares.

Então, uma vez feito todo esse trabalho de faxina, comecei a enxergar a estrutura do que era realmente feito e do que precisava ser feito. A partir daí, uma simplificação levou a outra, até que o ChatGPT 5.2 Codex fez o resto. Antes de 2 semanas atrás, isso ainda era feito inteiramente à mão e me deixou louco muitas vezes. É realmente só tinta segurando aquelas paredes, tinta descascada, e tentar limpá-la destruía muitas coisas porque nada neste software era modular (ou seja, encapsulado). Algo que você muda em um lugar tem consequências inesperadas em outro, e é por isso que temos encapsulamento, modularidade e padrões de projeto, porque a linguagem de programação C não foi projetada para aplicações desktop complexas como essa, e ela realmente precisa da disciplina do desenvolvedor para evitar tornar-se o pesadelo que é.

Isto é inteiramente vibecoding

Então a limpeza do histórico vinha acontecendo desde 2023, administrando o esgotamento e a depressão induzida pelo software. Isso é uma qualidade de vida de merda, você não tem ideia. Aqueles que acham que eu exagero não sabem o que envolve pandear com as fezes cerebrais dos outros por mais de 3 anos. Porque eu conheci uma época em que tudo aquilo era, se não melhor, ao menos menos complicado e mais administrável. Até que a loucura da COVID-19 atingiu, e idiotas ganharam tempo livre demais nas mãos, que usaram para destruir algo que estava mais ou menos funcionando.

E aí descobri o ChatGPT 5.2 Codex 2 semanas atrás, e o instalei dentro do editor VS Code. Então, levei 3 dias de trabalho para fazer todas as coisas que apresentei aqui. Sem o ChatGPT, teriam sido umas boas 3 semanas, mais a mexeção sem fim com os detalhezinhos do GTK. Vamos falar sobre a experiência.

Eu discordo daqueles que tentam nos fazer acreditar que a IA generativa é apenas uma ferramenta. Uma ferramenta só funciona na minha mão. Não quando eu durmo. Eu fiz o ChatGPT trabalhar para mim enquanto eu cozinhava o jantar (sim, ele é tão lento assim). Você não se comunica com uma ferramenta, você apenas a usa no melhor da sua capacidade. Em caso de falha, bem, alguns culpam a ferramenta, mas todos nós sabemos o que isso significa. O problema é que o ChatGPT não tem botões ou controles deslizantes, ele interpreta o que você diz a ele, e não necessariamente como você quer dizer. E, além disso, uma ferramenta não toma iniciativa. Bem, o ChatGPT certamente tem uma opinião sobre como o código deve parecer, e às vezes você precisa brigar com ele.

A IA generativa é um estagiário. Um estagiário não tem experiência e só sabe o que é ensinado na escola. Um estagiário pode trazer ideias novas e frescas que desafiam seus hábitos, e sugestões delirantes da mesma forma, que não são nem remotamente relevantes para o seu contexto e às vezes nem sequer viáveis. Mas um estagiário precisa trabalhar sob supervisão de perto e receber instruções claras e não ambíguas. O ChatGPT é muito mais um estagiário do que uma ferramenta.

O ChatGPT comete muitos erros, e eles são traiçoeiros porque estão enterrados no meio de coisas perfeitamente válidas. Ele tem algumas obsessões estranhas (como verificar NULL em todo ponteiro que já sabemos que não pode ser NULL). Então, você realmente precisa ficar de olho nele. Embora revisar e corrigir seus erros ainda seja mais rápido do que escrever todo o código eu mesmo, sem mencionar que meu primeiro problema de síndrome do túnel do carpo foi há 10 anos, então é sempre menos coisa para não digitar. Além disso, ele comete erros de lógica, mas nenhum erro de digitação, e pelo menos muito menos do que eu mesmo.

Mas onde o ChatGPT Codex brilha é em 2 coisas.

Primeiro, o tedioso jogo de fazer grep de funções pela base de código para descobrir (fazer engenharia reversa do) o ciclo de vida dos dados e verificar todos os locais de chamada para construir um modelo mental do que está acontecendo. Isso leva uma eternidade, é muito exigente cognitivamente, especialmente numa base de código tão ruim assim. O ChatGPT faz maravilhas para percorrer dezenas de arquivos, extrair padrões, descobrir o que poderia ser fatorado e seguir sequências de execução. Deixemos bem claro que, numa base de código bem mantida, isso não deveria ser uma necessidade, porque o código estaria autocontido em módulos, isolado do resto. Mas o ChatGPT ajudou muito a tornar as coisas mais modulares.

Segundo, tudo que envolve GTK e GLib. Esses são mal documentados na web, e muitos padrões idiomáticos de interação são conhecidos apenas pelos desenvolvedores do GTK. O ChatGPT obviamente ingeriu muito código de código aberto e pode produzir um código de interface gráfica boilerplate muito melhor do que eu poderia (ou me importaria de fazer). De qualquer forma, antes do ChatGPT, isso se transformava em sessões tediosas de googlar informações, e eu não consigo encontrar nenhuma informação técnica relevante no Google desde 2020 mais ou menos, quando eles mudaram seus algoritmos para questionar agressivamente tudo. Mas eu trabalho para resolver problemas, e todas as funções boilerplate de interface gráfica inicializando widgets e suas propriedades num estilo declarativo não são dignas da minha inteligência, é só tentar não introduzir erros de digitação.

Mas, para ter uma ideia melhor, aqui está o tipo de prompts que tive que dar a ele para construir o que acabei de apresentar:

agora, em _hm_try_merge_iop_order_topologically(), construa cedo na função uma GHashtable de todos os IDs de módulos ligados a mod_list, depois a dev_src->iop, depois a dev_dest->iop. Estas serão úteis para calcular a interseção de conjuntos mais tarde. não modifique dev_dest->iop_order_list. Para cada item na lista ordenada (item sendo um ID de nó ligado a um op de módulo e a um multi_name):

  1. descubra se existe uma instância de módulo correspondente em dev_dest->iop, se não, crie uma. Como dev_dest->iop já está inicializado e higienizado a montante, podemos assumir com segurança que todo módulo não encontrado deve ser inserido como uma nova instância. Se o ID da instância do módulo for encontrado na mod_list de entrada, todo o conteúdo do módulo (parâmetros, blendop, etc.) deve ser copiado da instância de origem para a instância de destino. Atenção às cópias profundas que precisam acontecer.
  2. sobrescreva todos os valores de module->iop_order com o novo número de índice que acabamos de encontrar resolvendo
  3. reconstrua dev_dest->iop_order_list do zero e atualize o module->multi_priority de acordo

agora, em dt_history_merge_module_list_into_image_advanced, o histórico temporário precisa ser construído da seguinte forma:

  1. desimplemente o caminho force_new_modules por enquanto, voltaremos a ele mais tarde e de forma diferente,
  2. construa um histórico temporário da seguinte forma: para cada módulo em mod_list:
    1. obtenha o item de histórico associado de dev_src->history (esse seria o último correspondente a este módulo na pilha de histórico),
    2. obtenha a informação de ordenação do pipeline (iop_order, instance, multi_priority) do módulo correspondente em dev_dest->iop
    3. atualize o item de histórico existente de dev_src->history com a ordenação do pipeline, já que ela pode ter mudado após a ordenação topológica, em relação ao item de histórico original,
    4. adicione esta entrada de histórico ao histórico temporário
  3. concatene o histórico temporário com dev_dest->history, no início ou no fim dependendo do modo append ou appstart.

Tente usar métodos de history.c e dev_history.c o máximo possível para o tratamento de histórico para/de módulo. Estenda os existentes se você só precisar de mudanças menores.

Não, reverta isso. Não é aceitável excluir entradas de histórico além do history_end em geral. O que quer que esteja em dev->history deve ir para o BD. Além disso, não é um problema porque o history_end também é salvo no BD. O problema aqui é que itens de histórico aleatórios são adicionados ao ler o histórico de volta do BD. Tudo até a escrita do histórico, que aconteceu em C, estava ok. Descubra por que obtemos entradas de histórico extras ao ler de volta do BD, comparado ao que temos no momento da escrita antes.

no fim de dt_history_merge em history_merge.c, quero que você mostre uma janela pop-up de relatório. Um rótulo de texto primeiro dirá “Copiando, mesclando pipeline no modo {MERGE_MODE} e histórico no modo {STRATEGY}”, onde {MERGE_MODE} depende de merge_iop_order (merge ou destination), e {STRATEGY} depende de strategy. Depois quero uma GtkTreeView em modo de lista, com 3 colunas:

  1. a origem da cópia, com ID da imagem e nome do arquivo (não o caminho completo),
  2. a sobrescrita,
  3. o destino da cópia, com ID da imagem e nome do arquivo.

Nas colunas 1. e 3., cada linha mostrará as instâncias de módulos, começando com sua ordem no pipeline, module->name e module->multi_name. Apenas módulos habilitados aparecerão. A coluna 2 desenhará uma seta entre as instâncias de origem e destino quando o histórico de origem sobrescreve o histórico de destino. Isso é feito verificando, no histórico de destino, se a última entrada que tem como alvo este módulo corresponde ao histórico de destino ou de origem. No caso de corresponder a ambos, não mostre nada, já que não é uma sobrescrita. Os nós do pipeline serão mostrados em ordem reversa para corresponder à ordenação da interface gráfica, já que é uma espécie de pilha de camadas. Ambos devem estar alinhados na parte de baixo, para que as etapas iniciais tenham a chance de estar na mesma linha até a topologia divergir entre os dois pipes

Uma coisa que descobri é que você pode definitivamente ser específico demais com o ChatGPT e levá-lo a um beco sem saída. Quando isso acontece, o melhor curso de ação é assumir o controle manualmente.

O custo de energia dessa coisa é insuportável, mas digamos que, dividido pelos cerca de 900 caras que deram estrela ao Ansel no Github (eu não tenho estatísticas de download), é pelo bem maior. É só uma forma mais eficiente de poupar o suco do meu cérebro para pensar sobre o que deve ser feito (projeto e arquitetura), em vez de como fazê-lo. Provavelmente não é como as crianças fazem vibecoding hoje em dia, no entanto.

Próximo: estilos.


Translated from English by : Claude. In case of conflict, inconsistency or error, the English version shall prevail.