workflow-visualizer

Renderiza fluxos de negócio como código Mermaid + fluxograma HTML interativo auto-contido (zero dependências, totalmente offline, sem requisições a CDN).

O que é workflow-visualizer?

Esta skill transforma descrições de processos, SOPs, cronogramas de produção ou linhas do tempo em fluxogramas interativos auto-contidos. Ela gera simultaneamente um diagrama HTML navegável (com suporte a pesquisa, filtro por status, temas claro/escuro, exportação para SVG/PNG) e o código Mermaid correspondente, que pode ser colado em ferramentas como Feishu, Yuque ou GitHub. A skill opera inteiramente offline, utilizando apenas módulos nativos do Node.js, sem dependências externas ou CDN. É ideal para desenvolvedores e agentes de IA que precisam visualizar fluxos de trabalho complexos, validar a estrutura do processo e compartilhar diagramas interativos com equipes técnicas ou de negócios, funcionando em ambientes restritos, sem acesso à internet.

Funciona com✓Claude Code✓Codex CLI✓Cursor
npx skills add jimku233/workflow-visualizer

Installed? Explore more Produtividade e Colaboração skills: steipete/gemini, steipete/gh-issues, steipete/skill-creator · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

workflow-visualizer:Fluxos de Negócio → Fluxogramas Interativos

Script de renderização: scripts/flow.mjs (relativo ao diretório desta skill) Caminho absoluto: scripts\flow.mjs Manual completo de campos e sintaxe: references/spec.md (leia antes de escrever a especificação)

1. Restrições Principais (Justificativas)

  1. Zero Dependências: Apenas módulos nativos node:. Não precisa de npm install, nem de instalar o mermaid.
  2. Totalmente Offline: O layout e a renderização são feitos com SVG próprio, sem referenciar nenhum CDN. Funciona mesmo sem internet, em redes internas ou isoladas (comandos como web_fetch/docs.deepseek.com não funcionam; qualquer solução que dependa de CDN falhará).
  3. Produto Auto-Contido: O .svg exportado contém <style> embutido, podendo ser exibido corretamente mesmo se enviado separadamente; o .html é um único arquivo, que abre ao dar duplo clique.

2. Uso em Três Passos

Passo 1: Extrair o Fluxo (essencial) Extraia do material real do usuário, não invente. Prioridade das fontes:

  • Documentos de plano/revisão no workspace (*.md, geralmente contêm seções como "Fluxo de Trabalho/SOP/Cronograma")
  • Descrição verbal do usuário (neste caso, marque incertezas como status: hold e escreva note informando "a confirmar")
  • Histórico de conversa ou logs

Ao extrair, capture sempre estes cinco elementos: etapa (quem faz o quê), janela de tempo, dependências, branches de decisão, pontos de risco/necessidade de aprovação manual.

Passo 2: Escrever a Especificação

  • Fluxo simples (linear com um ou dois branches) → Escreva Mermaid diretamente e salve como .mmd.
  • Com campos como tempo/responsável/status/descrição/grupos → Escreva JSON (mais denso em informação, permitindo painéis ricos).
  • Execute sample para obter uma especificação pronta como modelo:
node "scripts\flow.mjs" sample --out ".\out\meu-fluxo.json"

Passo 3: Validar + Renderizar

$sk = "scripts\flow.mjs"
node $sk check  ".\out\meu-fluxo.json"     # Verificação estrutural: nós isolados, inalcançáveis, sem fim, ciclos
node $sk render ".\out\meu-fluxo.json" --all --out ".\out\meu-fluxo.html"

O produto padrão é gerado no mesmo diretório do arquivo de entrada.

Um comando render gera duas variantes de layout (no mesmo HTML, escolhidas automaticamente pela tela, comutáveis manualmente no rodapé):

VarianteLayoutAdequação
Retrato portraitColuna única vertical (wrap: 0)Celular: rolagem vertical, largura total
Paisagem landscapeFluxo longo dividido em várias colunas (wrap: -1, proporção alvo 1.6)Computador: cabe em uma tela

Parâmetros comuns: --out <file.html>, --all (saída adicional <nome>-retrato.svg, <nome>-paisagem.svg, .mmd), --wrap N (número fixo de linhas por coluna na paisagem), --debug (imprime número de colunas/linhas de cada variante e membros de cada coluna).

Validar ambas as variantes (recomendado):

node $sk preview ".\out\meu-fluxo.html"   # Gera "Pré-visualização [para Computador].html": dois iframes lado a lado (celular e computador)

Abra-o no navegador de depuração para verificar rapidamente se ambas as variantes estão corretas. Para precisão em tamanhos de dispositivo reais, use simulação de dispositivo via CDP:

# Captura de tela para celular + computador, exibindo alturas reais de canvas/aside/footer e a variante selecionada automaticamente
node $sk\..\tools\devshot.mjs "http://127.0.0.1:8765/meu-fluxo.html" celular.png computador.png

# Para executar JavaScript na página e verificar interações (ex.: clicar em nós, pesquisar, abrir painel):
node $sk\..\tools\probe.mjs <url> sonda.js --w 390 --h 844 --mobile --touch

Ambas as ferramentas criam suas próprias abas temporárias e as fecham após o uso, sem interferir nas abas que o usuário está navegando (versões anteriores capturavam diretamente o primeiro page target, afetando a página que o usuário estava visualizando; lição registrada abaixo). No entanto, note que a nova aba aparecerá na janela do navegador do usuário e tomará o foco—se o usuário estiver usando aquele navegador de depuração, pergunte primeiro, caso contrário, haverá interrupção mútua.

3. Auto-Verificação Obrigatória Após Gerar (não confie apenas em "comando bem-sucedido")

Comando bem-sucedido ≠ diagrama legível. Faça pelo menos uma verificação visual usando browser-control:

# 1) Crie uma cópia com nome de arquivo ASCII para evitar problemas de codificação com caracteres chineses em file://
Copy-Item ".\out\meu-fluxo.html" ".\out\preview.html" -Force
# 2) Use o gerador de URI do sistema para obter o endereço file:// correto (codificação manual de percentuais é propensa a erros)
$u = [System.Uri]::new((Resolve-Path ".\out\preview.html").Path).AbsoluteUri
node browser-control\webctl.mjs shot $u "out\shot.png" 5000

Em seguida, olhe a imagem e confirme: textos não cortados, branches não se cruzando emaranhados, faixas de grupo não cobrindo nós.

Interações de clique também podem ser verificadas via script (webctl.mjs evalfile; atenção: deve ser uma expressão (function(){...})(), return simples causará Illegal return statement).

4. Para Visualização Humana: Como Abrir no Computador / Celular

Computador: Dê duplo clique no .html (arquivo único, offline); para colar em documentos, arraste o .svg.

Celular (HTML está no computador, navegador do celular não tem recurso "abrir arquivo local")—use acesso pela rede local, sem transferir arquivos:

node "scripts\serve.mjs" ".\out" 8765

O script imprimirá diretamente o endereço acessível pelo celular (VPNs, placas de rede virtuais e 169.254 serão marcados automaticamente como "ignorar"), no formato: Celular → http://<seu-IP-local>:8765/ ✔ Use este (WLAN)

Conecte o celular na mesma rede Wi-Fi, abra o endereço no navegador e clique no nome do arquivo. Pontos importantes:

  • Exponha apenas o diretório de saída, não a raiz do workspace.
  • No Windows, o firewall pode perguntar na primeira vez; marque "Rede privada" para permitir acesso de entrada; se não perguntar e o acesso não funcionar, provavelmente está bloqueado pelo firewall.
  • A página do diretório lista arquivos; no celular, você pode clicar em qualquer .html / .svg / .png.
  • Após a visualização, posso parar o servidor (é uma tarefa em segundo plano), ou você pode fechar a janela do terminal.

Fora da mesma rede / para enviar a terceiros: Envie o próprio arquivo HTML—ele é auto-contido e funciona offline; pode ser transferido via WeChat/QQ. Se quiser apenas a imagem, clique em "Exportar PNG" na página ou pressione Ctrl+P no navegador para salvar como PDF.

Operações no celular: Arraste com um dedo para mover, pinça com dois dedos para zoom, toque em um nó para ver detalhes, "Ajustar" no rodapé para ver o diagrama completo. Botões de retrato/paisagem ficam no lado esquerdo do rodapé (mostram apenas ícones no celular).

ÁreaFuncionalidade
Barra superiorMarca + título, caixa de pesquisa, botão "Todos os Fluxogramas" (canto superior direito), legendas de status (clique para filtrar)
Painel direitoQuando nenhum nó selecionado: visão geral do fluxo; ao pesquisar: lista de resultados primeiro (veja abaixo); após selecionar nó: cartão de detalhes + "Voltar à Visão Geral"
Barra inferiorAlternar retrato/paisagem, visão geral/detalhes (celular), zoom, ajustar, exportar SVG/PNG, tema claro/escuro, código-fonte Mermaid

Resultados de Pesquisa em Três Níveis (importante, não confunda com bug)

A pesquisa verifica simultaneamente título / id / hora / responsável / descrição / observações, mas no canvas apenas o título é visível. Portanto, os resultados dividem-se em dois tipos:

  • Resultados visíveis (título/id/hora/responsável) → Nó com borda azul, contagem normal
  • Resultados apenas em descrição/observações → Nó com borda laranja tracejada, contagem aparece como "2 resultados (1 apenas em descrição/observações)", com lista de resultados à direita (clique para navegar)

A cor de destaque usa a variável dedicada --hit (tema escuro #4a9dff, tema claro #2b7fe0), não pode usar --accent — no tema claro, a cor de destaque é terracota #bf6b3a, e usá-la faria os resultados ficarem com "borda vermelha", inconsistente com a descrição "borda azul" (já sofremos com isso).

Exemplo: Pesquisar copo no fluxo de exemplo resulta em 2 resultados — Abrir segundo posto de copos (título) e Reabastecimento da tarde ("copos" na descrição); pesquisar f encontrará nós com id f e quaisquer nós que contenham a letra f na descrição/observações. Cada nó é contado apenas uma vez, não é duplicação.

Tema Claro/Escuro: Configuração Global / Por Diagrama / Bloqueio (a mais recente prevalece)

Três chaves armazenadas no localStorage do navegador (todas armazenam JSON {mode, ts}, comparando timestamp, quem alterou por último prevalece):

ChaveQuem escreveSignificado
flowviz-theme"Configurações de Exibição" na página inicialConfiguração global: Seguir sistema / Sempre escuro / Sempre claro
flowviz-theme:<nome-do-arquivo>Botão claro/escuro na página do fluxogramaConfiguração individual do diagrama (valores: dark/light)
flowviz-lock:<nome-do-arquivo>"Bloquear cor" na página do fluxogramaBloqueia a cor do diagrama ({locked:true, mode})

Regras de efetivação (o script de bootstrap no <head> e o JavaScript da página são idênticos para evitar flash de cores):

  1. Se bloqueado → Usa a cor congelada no momento do bloqueio; configuração global e individual não afetam.
  2. Caso contrário, compara os timestamps da configuração global e individual; a mais recente prevalece.
  3. Se nenhuma existir → Segue o sistema (prefers-color-scheme), mudando em tempo real quando o sistema altera.

Portanto: Primeiro altere o individual, depois mude o global → Todos seguem o global; primeiro mude o global, depois altere o individual → Apenas aquele diagrama muda. Para alterar a cor após bloquear, é necessário desbloquear primeiro (clicar no botão claro/escuro enquanto bloqueado será impedido e exibirá um aviso).

Matriz de teste (8 passos, 0 exceções): Limpar → Global=claro → Diagrama=claro; Clicar claro/escuro no diagrama → Diagrama=escuro (grava chave individual); Mudar Global=claro → Diagrama=claro (configuração global atualizada, sobrescreve individual); Clicar claro/escuro no diagrama novamente → Apenas este diagrama fica escuro; Bloquear → Mesmo alterando configuração global, permanece escuro bloqueado; Clicar claro/escuro enquanto bloqueado → Impedido com aviso "Desbloqueie primeiro"; Desbloquear → Volta à cor clara global; Cartão na página inicial exibe emblema "🔒 Bloqueado Claro".

Outros pontos:

  • Chamar setItem repetidamente com a mesma chave é uma sobrescrita, não acumula (testamos 12 cliques no botão claro/escuro e Object.keys(localStorage) ainda tinha apenas 1 item, totalizando 18 bytes), não é necessário "deletar a anterior".
  • A preferência fica armazenada no navegador do dispositivo, não é afetada pela reinicialização do servidor; apenas ao mudar de IP/porta (mudando de origin), limpar dados do navegador ou trocar de navegador a configuração é reiniciada.
  • O tema claro segue o estilo "Milênio Bege", não é branco puro: fundo #f2ebdd, painéis #fdfaf3, texto #33302a, destaque terracota #bf6b3a, textura interna #e6dcc8 (tema escuro mantém o conjunto original).

Botões do Topo

🏠 Início Volta à página de lista de arquivos (href="./") | Todos os Fluxogramas Abre gaveta lateral direita | Caixa de pesquisa | Legenda de status | Claro/Escuro Altera este diagrama | Bloquear Cor Congela a cor do diagrama. Na gaveta, a página atual é renderizada como não-link (clicar nela não faz nada, evitando "achei que estava quebrado"), a lista é sempre ordenada por nome de arquivo, portanto fetch ao atualizar a lista não altera a ordem.

O botão no canto superior direito abre a gaveta lateral direita, listando todos os fluxogramas gerados por esta skill no mesmo diretório (sempre ordenados por nome de arquivo; a página atual é marcada como "Atual" e renderizada como não-link não clicável), clicando em um item você alterna para ele. A identificação é feita pelo atributo data-flow-viz="1" na página, portanto outros HTMLs não serão incluídos.

  • O arquivo de lista é index.json no diretório, recriado automaticamente a cada render (também pode ser recriado manualmente com node flow.mjs index <diretório>).
  • Quando acessado via HTTP, a gaveta faz fetch da lista em tempo real para atualização; ao abrir o arquivo diretamente via arquivo local, usa o snapshot embutido na página (cada página contém uma cópia, portanto a alternância funciona localmente).

Interação da Pesquisa (Três Correções para "Resultado Não Aparece")

  1. Expansão automática do painel de resultados: Ao pesquisar e encontrar resultados sem um nó selecionado atualmente, setPanel(true) é chamado — no celular, os resultados ficam ocultos na gaveta inferior recolhida; não expandir equivale a nenhum feedback (esta é a causa raiz de "pesquisei e não vi nada").
  2. Anterior / Próximo: A barra de título da área de resultados possui ↑ / ↓ e contador Atual/Total; na caixa de pesquisa, Enter = próximo, Shift+Enter = anterior; Esc limpa.
  3. Foco Automático (desligável): A área de resultados possui um interruptor "Foco automático no resultado". Quando ativado:
    • Se a caixa envolvente dos resultados couber na visualização e o zoom necessário for ≥ zoom atual × 0.6 → Enquadra todos os resultados (múltiplos resultados visíveis na mesma tela).
    • Caso contrário (resultados muito distantes) → Não reduz o zoom, mantém o zoom atual centralizado no "resultado atual", navegando com ↑/↓ um por vez.
    • Dessa forma, o texto nunca é reduzido a ponto de ficar ilegível para tentar enquadrar dois resultados distantes.
  4. Durante a pesquisa, a classe .searching é adicionada ao svg: não-resultados esmaecem para 14% (linhas 10%, faixas de grupo 35%), resultados ganham borda mais grossa (3px).
  5. Movimentação programática (foco/navegação) adiciona .vp-anim à #vp (transição de 0.28s); arrasto manual não é afetado.

Fluxogramas com Nomes Duplicados: Primeiro Distinguir "Mesmo Conteúdo" de "Apenas Mesmo Nome"

Quando vários arquivos com o mesmo título aparecem (fonte típica: deletar e restaurar um arquivo, gerando xxx - copia.html), o topo da página inicial exibe um aviso, com tratamento baseado em conteúdo:

SituaçãoCritérioAção Oferecida
Mesmo conteúdo (mesmo fluxo renderizado duas vezes)Impressão digital semântica do fluxo idêntica"Manter apenas o mais recente, mover o restante para pasta de lixo"
Conteúdo diferente (mesmo nome, versões diferentes)Impressão digital diferenteNão deleta, apenas oferece "Renomear…" (sugestão padrão: NomeOriginal (Versão Antiga Data).html)
  • Impressão digital = semântica do fluxo, considerando apenas titulo/subtitulo/direcao/grupos/nos/arestas, ignorando campos de nível de página como selfFile, flows, slug. Portanto, "dois arquivos do mesmo fluxo renderizados em momentos diferentes" têm a mesma impressão digital, enquanto "apenas metadados da página alterados" não são erroneamente considerados conteúdo diferente.
  • Deletar sempre significa "mover para pasta de lixo": Cria-se .trash-YYYY-MM-DD/ no mesmo diretório, e o HTML, junto com seus SVG / MMD (reconhecidos pelo nome normalizado), são rename para lá, podendo ser recuperados a qualquer momento.
  • A ação é processada pelo endpoint do backend POST /__flow/act ({op:'trash', remove:[...]} / {op:'rename', file, to}):
    • Apenas chamadas de 127.0.0.1 são permitidas (celular, rede local tudo retorna 403) — caso contrário, a capacidade de "deletar seus arquivos" ficaria exposta via Wi-Fi.
    • Nomes de arquivo passam por lista de permissões: sem separadores de caminho, comprimento ≤ 200, devem terminar com .html; conflitos de nomes retornam 409.
    • No modo celular / file:// local, os botões não são exibidos, apenas o aviso (nesses cenários não há backend gravável).
  • O cartão ainda exibe o nome do arquivo (quando títulos são duplicados) para identificação manual.

Duas Formas de Abrir (Ambas Devem Levar à Página Inicial)

FormaComo acessar a página inicialOrigem da página inicial
Duplo clique local (file://)🏠 no fluxograma → ./index.htmlindex.html estático escrito pelo render (a lista de diretórios nativa do navegador mostra apenas nomes de arquivo, portanto esta é necessária)
Rede local / HTTP🏠 → ./Gerado dinamicamente por serve.mjs
  • render recria o index.html a cada execução, que compartilha o mesmo código de renderização da página inicial HTTP (função buildHome() exportada por serve.mjs; serve.mjs só inicia o servidor quando executado diretamente, quando importado apenas fornece funções).
  • Diferenças entre a versão estática e a HTTP: a versão estática mostra 📁 NomeDoDiretorio (Pasta Local) no breadcrumb, o emblema exibe "Página de Fluxograma", e o rodapé inclui uma mensagem "Modo arquivo local: para acessar pelo celular, inicie o servidor primeiro".
  • O botão 🏠 é ciente do protocolo: quando location.protocol==='file:', altera o href para ./index.html.
  • O index.html não aparece na lista de cartões (versões antigas mostravam, com o primeiro cartão chamado "index"); HTMLs que não estão na lista nem têm flow-stats analisável são agrupados em "Outros arquivos".

Página Inicial (Mesma Página para Computador e Celular)

  • Breadcrumb: 🏠 Fluxogramas / Subdiretório (Atual), cada nível clicável.
  • Bloco de caminho do disco: Exibe diretamente .\out, com botão "Copiar Caminho" — não precisa adivinhar para localizar o arquivo original.
  • Filtragem + Ordenação + Resumo: Filtra por título/nome do arquivo; ordenação pode ser "Recentes Primeiro / Por Nome / Por Data de Modificação"; resumo exibe 3 fluxogramas · 36 etapas / 47 conexões · 4 pontos de atenção (2 fluxogramas).
  • Abertos Recentemente: Ao clicar em um cartão, registra em flowviz-recent (máx. 8 itens), que fica no topo por padrão com o emblema "Aberto Recentemente".
  • Cartões também exibem emblemas "🔒 Bloqueado X" e "Definido Individualmente como X" (lendo o localStorage local).
  • Texto adaptado ao dispositivo: No mesmo HTML, duas mensagens são escritas, alternadas por @media (hover:none),(pointer:coarse) (mais preciso que largura; celular em modo paisagem também funciona):
    • Computador (.only-pc): 🖱️ Clique no cartão para abrir. No diagrama: rolagem com zoom, arraste para mover; canto superior direito "Configurações de Exibição" alterna claro/escuro
    • Celular (.only-touch): 📱 Clique no cartão para abrir: arraste com um dedo, pinça com dois, toque em uma etapa para detalhes…
    • Emblema do cartão "Clique aqui no celular" adiciona classe .touchonly, não aparece no computador; o emblema da página de comparação muda para "Página de Comparação Lado a Lado · Para Computador" (compreensível para ambos).
    • Dicas no rodapé/painel lateral da página do fluxograma: computador vê Rolagem com zoom · Arraste para mover · Esc, celular vê Arraste com um dedo · Pinça com dois.
  • Fluxogramas com Nomes Duplicados (ex.: xxx - copia.html e xxx.html): só pelo título não dá para diferenciar → esses cartões exibem o nome do arquivo abaixo do título.
  • Miniaturas são combinadas por nome normalizado: removendo espaços e -retrato/-paisagem antes de comparar; portanto, Exemplo Fluxo - copia.html pode combinar com Exemplo Fluxo-paisagem - copia.svg (diferença na posição do sufixo também funciona).
  • Resposta da página de diretório deve incluir cache-control: no-store: ela é gerada dinamicamente; se o navegador armazenar em cache, o usuário não verá a versão mais recente (já sofremos: usuário via uma lista antiga e simples, achando que "a página inicial é muito simples, sem miniaturas").

5. Regras Práticas para Desenhar Fluxogramas de Negócio

  1. Granularidade dos nós: Um nó = uma ação entregável ("Escrever roteiro" é um nó; "Pensar seriamente" não é). 8–16 nós por diagrama é o mais confortável; acima de 20, divida em diagrama principal + subfluxos.
  2. Use os estados: done/doing/todo/risk/hold não são decorativos. Em fluxos reais, bloqueios como "Aguardando confirmação/Esperando material" devem ser marcados como hold; "Requer aprovação manual" como risk — é aqui que o fluxograma supera a documentação.
  3. Nós de decisão apenas para branches reais: Use {...} para decisões que levam a caminhos diferentes. Para simplesmente "verificar e continuar", use milestone.
  4. Janelas de tempo preferencialmente vindas do material, não invente. Se o material não tiver, deixe em branco, não adivinhe.
  5. Nós de copyright/conformidade/linha vermelha devem ser explicitamente desenhados e descritos em note; caso contrário, o diagrama pode parecer correto, mas esconde riscos.
  6. Cadeias longas não usem direção LR: Uma cadeia de 13 passos em LR resulta em uma faixa de 1980×489 (proporção 4:1), que em uma tela de 1100px de largura, com fit, reduz a 0.55x, tornando o texto ilegível; já a versão paisagem (com colunas) pode comprimir o mesmo diagrama para cerca de 1.6:1, cabendo em uma tela sem reduzir o texto. Experiência: Para fluxos lineares, use versão paisagem (colunas), não LR.
  7. Como escolher entre as duas variantes: Celular: veja retrato (uma coluna, rolagem para baixo, intuitivo para tela vertical); Computador: veja paisagem (colunas, visão geral). O limiar de autoVariant() é largura < 760px ou proporção < 1.08 → retrato.
  8. A direção de leitura das colunas ainda é para baixo: Não faça serpentina (alternando para cima e para baixo), pois colunas invertidas confundem a direção das setas. Conexões entre colunas usam espaços entre colunas (quando há apenas um nó por linha) ou corredor inferior (quando há vários nós por linha), nunca atravessam nós.
  9. Grupos que cruzam colunas são automaticamente divididos em segmentos com "(cont.)", evitando que um retângulo envolva nós não relacionados.

6. Registro de Validação (2026-10-04)

  • Formatos de entrada: JSON / Subconjunto Mermaid / Blocos de código ```mermaid em .md, todos os três caminhos testados com sucesso.
  • Round-trip Mermaid: Análise → Reexportação .mmd, preservando nós {}, setas tracejadas -.->, labels de aresta -->|sim|, grupos subgraph corretamente.
  • Layout de duas variantes (fluxo de exemplo com 13 nós / 13 arestas / 3 grupos): Retrato 533×1936 (12 linhas em coluna única); Paisagem 1119×710 (automático 3 colunas × 4 linhas, proporção 1.58, cabe em uma tela). Fluxo de exemplo com 7 nós: Retrato 533×890, Paisagem 806×459 (automático 2 colunas).
  • Seleção automática de variante (medição real com simulação de dispositivo CDP): 390×844 → show=portrait; 1440×820 → show=landscape.
  • Layout para celular (medição real 390×844): canvas 364 / painel de visão geral 253 / rodapé 81, conteúdo da visão geral renderizado completamente (panelLen 1533).
  • Gestos de toque (medição real com TouchEvent sintético via CDP): Arrastar com um dedo ✓, Pinça com dois ✓, Tocar em nó seleciona e abre "← Voltar à Visão Geral | s | T 12:00 Início | Ponto de Partida | Concluído" ✓. Caminho do mouse também passou nos três testes.
  • Interação: Clicar em nó → Cartão de detalhes + realce de upstream/downstream + resto escurece; Filtro de legenda escreve data-hide-status; Contagem de acertos de pesquisa; Zoom com teclas +/0 e ajuste para 100%; Alternância de tema claro/escuro; Ambas as variantes mantêm estado de seleção/pesquisa/filtro.
  • Verificação de exceções durante carregamento (Runtime.exceptionThrown): Ambas as páginas de exemplo 0 exceções.
  • Versão embelezada: Nós em estilo cartão (barra vertical de status + sombra suave), emblemas de tipo, pílulas de tempo, bypass ortogonal arredondado, labels de aresta em cápsula, labels de grupo em cápsula, painel direito com visão geral do fluxo (contagem + filtro de status + lista de pontos de atenção), textura pontilhada, barra de ferramentas com ícones.

Armadilhas Enfrentadas (já corrigidas no código, registradas para economizar tempo no futuro)

  1. Nós de decisão Mermaid não funcionavam: touch() escrevia o campo shape, normalize() lia kind — nomes de campo divergentes, losangos eram desenhados como retângulos.
  2. Aspas em labels eram removidas: Marcar "adaptado" virava Marcar "adaptado, pois as aspas no início e fim eram removidas incondicionalmente; corrigido para remover apenas se forem iguais no início e fim.
  3. Porcentagem de zoom não correspondia à realidade: O viewBox do SVG fazia sua própria escala meet, e o transform original era sobreposto, resultando em "100%" exibindo 26% na prática. Corrigido para calcular unificadamente no espaço de pixels do elemento (view{K,TX,TY}) e depois re-sintetizar.
  4. Set-Content no PowerShell adicionava BOM UTF-8, quebrando #!/usr/bin/env node com SyntaxError. Não use shell para escrever arquivos deste script.
  5. b.members indefinido: Objeto de faixa de grupo não passava members, diagramas com labels de grupo quebravam.
  6. Ajuste para caber toda a tela reduzia fluxogramas longos a 26%: Corrigido para "se a diferença entre ajuste total e largura total for menor que 1.8x, use ajuste total; caso contrário, largura total + alinhamento ao topo".
  7. Losango com texto comprimido: Após recalcular as dimensões dos nós de decisão com base na condição do retângulo inscrito, É um problema de sintaxe ou discurso? passou a caber em uma linha.
  8. Conflito de id com duas variantes: <g id="vp"> em renderSvg não tinha sufixo de variante; getElementById('vp-landscape') no cliente retornava null → paint() lançava exceção, interrompendo toda a inicialização, deixando o painel de visão geral em branco (sintoma: aside com apenas 25px). Todos os ids (defs/filter/pattern/marker/vp) em ambas as variantes devem ter o sufixo -${vid}.
  9. Semântica do parâmetro wrap invertida: Deveria significar "linhas por coluna", sem quebra deveria ser R = L em vez de R = 1 (R=1 resultava em uma faixa horizontal de uma coluna por nível, fazendo a versão retrato ter 3476×101).
  10. Reprodução de exceções CDP engana: Após Runtime.enable, exceções do documento anterior são enviadas juntas. Para determinar se a página atual realmente tem erro, primeiro navegue para about:blank com Page.navigate e depois para a página alvo; caso contrário, erros da página antiga são atribuídos à nova.
  11. Verificação de toque para seleção precisa de ajuste prévio: Após gestos, o nó alvo pode ter sido movido para fora da área visível; elementFromPoint naturalmente não o encontrará — é um problema do script de teste, não da página.
  12. Validação CDP tomava a aba do usuário: webctl shot / capturar o primeiro page target navegava na aba que o usuário estava visualizando, causando interrupção mútua. Corrigido para tools/cdp.mjs com withOwnTab() — criar aba temporária com Target.createTarget, esperar por ela em /json/list, fechar com Target.closeTarget após o uso. Mesmo assim, a nova aba ainda toma o foco, pergunte antes se o usuário estiver usando o navegador.
  13. Resultados "extras" de pesquisa não são bug: Pesquisar copo retorna 2 resultados porque a pesquisa também verifica descrição/observações ("calibração de copo") e id (nó j). A correção não é alterar o número, mas marcar os resultados invisíveis (círculo laranja tracejado + contagem separada + lista de resultados).
  14. Abas em segundo plano não podem ser capturadas: Target.createTarget({background:true}) não rouba o foco, mas abas em segundo plano não geram frames; Page.captureScreenshot trava eternamente (testado: timeout de 120s). Para capturar tela, é necessário aba em primeiro plano (que rouba o foco); abas em segundo plano servem apenas para medições geométricas puras (getBoundingClientRect / getComputedStyle funcionam normalmente). Além disso, transições CSS não avançam em abas em segundo plano — antes de medir geometria, injete *{transition:none!important}, caso contrário, você medirá o ponto inicial da animação (erroneamente julguei rect.x=398 da gaveta como "não aberta", quando na verdade era devido a throttle).
  15. Produtos "para computador" também recebiam emblema "Clique aqui no celular": Os emblemas da lista de arquivos consideravam apenas a extensão. Agora, arquivos que começam com 【/_ são marcados individualmente como "Para computador, não clique no celular" e desabilitados — a condição deve ser alterada junto com o texto de exibição.
  16. Não coloque produtos de validação no diretório servido: shot-*.png, scripts de sonda aparecem na lista de arquivos do celular. Unifique em um diretório não servido (ex.: out/_shots/), não no diretório de produtos servido por serve.mjs.
  17. Lista da gaveta reordenando sob o dedo = clicar no item errado: O snapshot embutido na página tinha "página atual primeiro", mas fetch('index.json') retornava ordenado por mtime — ao abrir a gaveta, cerca de 100ms depois a lista pulava, e o usuário clicava em um item diferente do desejado, resultando em "clicou e não aconteceu nada / não voltou". Correção em duas frentes: Lista sempre ordenada por nome de arquivo (snapshot e lista sempre na mesma ordem), página atual renderizada como não-link. Lição: Qualquer lista que é "atualizada assincronamente após abrir" deve garantir ordem estável.
  18. Alterar serve.mjs requer reiniciar o servidor: O template da página está na memória do processo; se não reiniciar, fica na versão antiga (perdi tempo verificando "por que a página inicial não tem h1").
  19. Página de diretório sem cache-control fazia o usuário ver sempre a página inicial antiga: O usuário disse duas vezes "a página inicial do computador é muito simples / não tem miniaturas", mas na verdade eu já havia alterado para cartões + miniaturas, apenas o navegador usava a resposta antiga em cache. HTML gerado dinamicamente deve ter no-store.
  20. Serviço iniciado separadamente com Start-Process não sobrevivia ao fim do comando: O sandbox limpava a árvore de processos ao finalizar o comando, fazendo o serviço desaparecer silenciosamente (na próxima requisição, "não foi possível conectar ao servidor remoto"). Use harness de tarefa em segundo plano ou peça ao usuário para executar no terminal.
  21. Não use JSONs com nomes semelhantes no diretório como entrada: Havia um fluxo-甲.json no diretório, com nome parecido com fluxo-甲, mas conteúdo era a especificação de fluxo-乙; ao executar render, sobrescreveu o fluxo-甲.html existente com conteúdo diferente. Antes de renderizar, verifique o title/nós do JSON, e dê preferência à entrada com o mesmo nome do produto.
  22. Aspas retas em comentários em chinês truncam strings JavaScript com aspas duplas (" // diz respeito à lista de "nomes de arquivo" puro" → termina prematuramente). Caí na mesma armadilha três vezes: em comentários em chinês, ou não use aspas, ou use 「」.
  23. Em file://, não existe "página inicial do diretório": href="./" local cai na lista de nomes de arquivo do navegador, mostrando apenas nomes. A página de diretório gerada dinamicamente deve ter um index.html estático separado, e o botão de início deve alternar o alvo com base no protocolo.
  24. Sem navegador de depuração, como capturar tela: No sandbox, o Edge não inicia (pipe de nome mojo rejeitado), mas msedge --headless=new --screenshot=... --window-size=W,H <url> funciona (requer uma permissão irrestrita). Consegue capturar páginas file:// locais e simular celular/computador por tamanho, sendo essencial para diagnosticar "cartões não aparecem na primeira tela do celular" e "resumo calcula 0".
  25. Em template strings, expressões regulares precisam de barra invertida dupla: \d/\s em template strings degeneram para d/s, tornando a regex gerada na página inválida (o resumo já mostrou "0 etapas no total". Use \\d, \\s em scripts de página.
  26. Texto não deve ser fixo por dispositivo: Na página inicial do computador, "Clique aqui no celular" aparecia; no fluxograma no celular, instruções como "Rolagem com zoom / Duplo clique / Esc" eram exibidas — tudo erro de correspondência de dispositivo. A solução é escrever duas versões no mesmo local (.only-pc / .only-touch) + @media (hover:none),(pointer:coarse) para alternar — usar método de entrada é mais preciso que largura de tela (celular em modo paisagem, janela estreita no computador não causam erro).
  27. Fluxogramas com nomes duplicados no mesmo diretório têm cartões idênticos: xxx - copia.html e xxx.html têm o mesmo título, usuário não distingue. Quando os títulos são duplicados, exiba o nome do arquivo abaixo; miniaturas também devem ser combinadas pelo nome normalizado (removendo espaços e -retrato/-paisagem).
  28. \n em template strings vira quebra de linha real: Em JavaScript de página escrito em templates como homeScript(), '...\n\n' + x vira uma string literal com quebras de linha reais → erro de sintaxe no script da página (node --check verifica serve.mjs, mas não detecta isso). Use \\n. Método de verificação: Extraia os dois <script> gerados por buildHome() e execute node --check em cada um (adicionei esta etapa de regressão).
  29. Determinar "mesmo conteúdo" deve ser por semântica, não por hash do arquivo: O hash da página inteira será necessariamente diferente (carimbo de data/hora de renderização, …).

[truncado para tradução]

Habilidades Relacionadas