Construindo a tela de detalhes de projeto do Greenhouse
Greenhouse é meu gerenciador de projetos criativos, aquele que força cooldowns e rotação em vez de deixar você empilhar ideias pela metade para sempre. O dashboard já listava projetos ativos em três zonas: prontos hoje, lista de trabalho, esfriando. Mas você não conseguia clicar num projeto. Não havia como ver a contagem regressiva do cooldown, o histórico de toques, ou de fato mover o projeto adiante no pipeline.
O plano
Duas coisas precisavam ser decididas antes de eu escrever qualquer código, e nenhuma era óbvia só olhando o código.
Primeiro: avançar um projeto para o próximo estágio conta como um “toque”? Tocar é a única coisa que inicia o cooldown de 7 dias neste app, e a função move_project_to_stage existente deliberadamente não registra um toque. Eu tinha que escolher um lado. Fui com sim, avançar implica um toque, na teoria de que mover um projeto para frente já é evidência de uma sessão de trabalho concluída.
Segundo: o que acontece no último estágio? O schema já tinha um status Released e um timestamp released_at parados sem uso. Nada os definia. Adicionei uma ação “Publicar” no estágio terminal que finalmente conecta os dois.
O desvio
No meio do caminho, parei e me perguntei se o cooldown de 7 dias era mesmo o número certo. Deveria ser mais curto? O cooldown deveria existir, ou o app deveria só impor a ordem dos toques e confiar em mim para tocar com honestidade?
Trabalhar nisso revelou algo que eu tinha embaralhado: rotação (o projeto tocado há mais tempo sobe ao topo) e cooldown (um piso mínimo de tempo depois de um toque) não são ideias concorrentes. Rotação já é como a lista de trabalho ordena. Cooldown é só um piso parafusado em cima disso. Então a pergunta real não era “cooldown ou rotação”, era “quão longo deveria ser o piso”. E rotação pura sem piso desmorona exatamente onde me machucaria: com só um ou dois projetos ativos, não há para onde rotacionar, então a “folga” entre toques é só a velocidade com que eu clico de um lado para o outro.
Esse reenquadramento tornou a decisão real fácil: baixar o padrão de 7 dias para algo mais curto, manter intacta a ideia de cadência forçada, e arquivar a pergunta “e se fosse código de honra em vez disso” como um spike separado, em vez de deixar ela sequestrar a funcionalidade que eu estava de fato construindo.
Construindo
O lado Rust precisava de duas novas funções de engine: advance_stage (toca, depois move a pasta para o próximo estágio na lista ordenada de estágios da config) e release_project (vira o status para Released, marca o timestamp). As duas compõem peças já existentes em vez de reinventar. No lado Svelte, o nome de um card de projeto virou um botão de verdade, abrindo uma nova tela de detalhes com o nome do estágio, a contagem regressiva do cooldown, o histórico de toques, e os controles de avançar/publicar.
A parte que eu de fato quero escrever com calma algum dia: verificar isso. Este app tem um harness de WebDriver que dirige o binário compilado real através de cliques reais e chamadas IPC reais, não mocks. A pegadinha é que uma sessão de WebDriver ao vivo não consegue avançar o relógio, e tanto maturação de ideia quanto cooldown são timers de vários dias. Então não há como ir de “capturar uma ideia” a “clicar através dela” dentro de uma única execução de teste.
A correção foi popular o banco SQLite diretamente com sqlite3 antes de abrir o app, inserindo um projeto ativo já no estágio que o teste precisava. Isso não é trapaça; todo comando ainda roda de verdade assim que o app abre, eu só pulei a parte em que ficaria esperando sete dias por um timer.
Onde parou
As duas funções de engine têm testes unitários, o componente Svelte tem testes com mockIPC e uma checagem automatizada de acessibilidade, e o script de WebDriver clica através de avançar o estágio de um projeto e publicar um no estágio terminal contra um binário real. Prova em screenshot e todas as checagens de sempre (clippy, fmt, verificação de tipos, build) estão limpas.
A melhor parte da sessão, porém, não foi o código. Foi me pegar prestes a mudar um mecanismo central do app numa tangente, dar um passo atrás, e perceber que a reclamação real tinha uma correção bem menor logo ao lado.
Leitura relacionada
Rescue-or-Keep: construindo a zona de vault para o Greenhouse
Agora arquivar no vault realmente move seus arquivos, e um "Keep Vaulted" que não era um no-op de verdade - reagendar teria resetado o timestamp que a revisão diária usa para ordenar.
Construindo o Greenhouse: as primeiras telas
O arco da UI: uma shell de assistente construída antes de suas paredes existirem, um fluxo de captura removido por estar errado, e os buracos entre marcações verdes e a verdade.
O scan que se ofereceu para importar a si mesmo
Um scanner de importação que encontrou a própria estrutura interna do app, e depois voltou a oferecer uma pasta que tinha acabado de importar, duas versões da mesma conversa que faltava entre camadas.
Você também pode achar útil
Proton Mail
E-mail criptografado de ponta a ponta, com arquitetura de acesso zero.
Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).
Saiba maisProton Drive
Armazenamento em nuvem criptografado, da equipe por trás do Proton Mail.
Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).
Saiba maisAdGuard para iOS
Bloqueio de anúncios e rastreadores em todo o sistema no iOS, sem necessidade de um servidor DNS separado.
Como afiliado da AdGuard, ganho com compras qualificadas.
Saiba mais