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)
- Zero Dependências: Apenas módulos nativos
node:. Não precisa denpm install, nem de instalar o mermaid. - 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á). - Produto Auto-Contido: O
.svgexportado 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: holde escrevanoteinformando "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
samplepara 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é):
| Variante | Layout | Adequação |
|---|---|---|
Retrato portrait | Coluna única vertical (wrap: 0) | Celular: rolagem vertical, largura total |
Paisagem landscape | Fluxo 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).
| Área | Funcionalidade |
|---|---|
| Barra superior | Marca + título, caixa de pesquisa, botão "Todos os Fluxogramas" (canto superior direito), legendas de status (clique para filtrar) |
| Painel direito | Quando 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 inferior | Alternar 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):
| Chave | Quem escreve | Significado |
|---|---|---|
flowviz-theme | "Configurações de Exibição" na página inicial | Configuração global: Seguir sistema / Sempre escuro / Sempre claro |
flowviz-theme:<nome-do-arquivo> | Botão claro/escuro na página do fluxograma | Configuração individual do diagrama (valores: dark/light) |
flowviz-lock:<nome-do-arquivo> | "Bloquear cor" na página do fluxograma | Bloqueia 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):
- Se bloqueado → Usa a cor congelada no momento do bloqueio; configuração global e individual não afetam.
- Caso contrário, compara os timestamps da configuração global e individual; a mais recente prevalece.
- 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
setItemrepetidamente com a mesma chave é uma sobrescrita, não acumula (testamos 12 cliques no botão claro/escuro eObject.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.jsonno diretório, recriado automaticamente a cadarender(também pode ser recriado manualmente comnode flow.mjs index <diretório>). - Quando acessado via HTTP, a gaveta faz
fetchda 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")
- 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"). - Anterior / Próximo: A barra de título da área de resultados possui
↑ / ↓e contadorAtual/Total; na caixa de pesquisa, Enter = próximo, Shift+Enter = anterior;Esclimpa. - 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.
- 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). - 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ção | Critério | Açã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 diferente | Nã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 comoselfFile,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ãorenamepara 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)
| Forma | Como acessar a página inicial | Origem da página inicial |
|---|---|---|
Duplo clique local (file://) | 🏠 no fluxograma → ./index.html | index.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 |
renderrecria oindex.htmla cada execução, que compartilha o mesmo código de renderização da página inicial HTTP (funçãobuildHome()exportada porserve.mjs;serve.mjssó 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.htmlnã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êmflow-statsanalisá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.
- Computador (
- Fluxogramas com Nomes Duplicados (ex.:
xxx - copia.htmlexxx.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/-paisagemantes de comparar; portanto,Exemplo Fluxo - copia.htmlpode combinar comExemplo 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
- 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.
- Use os estados:
done/doing/todo/risk/holdnão são decorativos. Em fluxos reais, bloqueios como "Aguardando confirmação/Esperando material" devem ser marcados comohold; "Requer aprovação manual" comorisk— é aqui que o fluxograma supera a documentação. - Nós de decisão apenas para branches reais: Use
{...}para decisões que levam a caminhos diferentes. Para simplesmente "verificar e continuar", usemilestone. - Janelas de tempo preferencialmente vindas do material, não invente. Se o material não tiver, deixe em branco, não adivinhe.
- 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. - 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, comfit, 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. - 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. - 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.
- 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
```mermaidem.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|, grupossubgraphcorretamente. - 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+/0e 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)
- Nós de decisão Mermaid não funcionavam:
touch()escrevia o camposhape,normalize()liakind— nomes de campo divergentes, losangos eram desenhados como retângulos. - Aspas em labels eram removidas:
Marcar "adaptado"viravaMarcar "adaptado, pois as aspas no início e fim eram removidas incondicionalmente; corrigido para remover apenas se forem iguais no início e fim. - Porcentagem de zoom não correspondia à realidade: O
viewBoxdo SVG fazia sua própria escalameet, e otransformoriginal 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. Set-Contentno PowerShell adicionava BOM UTF-8, quebrando#!/usr/bin/env nodecomSyntaxError. Não use shell para escrever arquivos deste script.b.membersindefinido: Objeto de faixa de grupo não passavamembers, diagramas com labels de grupo quebravam.- 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".
- 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. - Conflito de id com duas variantes:
<g id="vp">emrenderSvgnã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:asidecom apenas 25px). Todos os ids (defs/filter/pattern/marker/vp) em ambas as variantes devem ter o sufixo-${vid}. - Semântica do parâmetro
wrapinvertida: Deveria significar "linhas por coluna", sem quebra deveria serR = Lem vez deR = 1(R=1 resultava em uma faixa horizontal de uma coluna por nível, fazendo a versão retrato ter 3476×101). - 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 paraabout:blankcomPage.navigatee depois para a página alvo; caso contrário, erros da página antiga são atribuídos à nova. - 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;
elementFromPointnaturalmente não o encontrará — é um problema do script de teste, não da página. - 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 paratools/cdp.mjscomwithOwnTab()— criar aba temporária comTarget.createTarget, esperar por ela em/json/list, fechar comTarget.closeTargetapós o uso. Mesmo assim, a nova aba ainda toma o foco, pergunte antes se o usuário estiver usando o navegador. - Resultados "extras" de pesquisa não são bug: Pesquisar
coporetorna 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). - 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.captureScreenshottrava 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/getComputedStylefuncionam 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 julgueirect.x=398da gaveta como "não aberta", quando na verdade era devido a throttle). - 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. - 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 porserve.mjs. - 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. - Alterar
serve.mjsrequer 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"). - Página de diretório sem
cache-controlfazia 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 terno-store. - Serviço iniciado separadamente com
Start-Processnã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. - Não use JSONs com nomes semelhantes no diretório como entrada: Havia um
fluxo-甲.jsonno diretório, com nome parecido comfluxo-甲, mas conteúdo era a especificação defluxo-乙; ao executarrender, sobrescreveu ofluxo-甲.htmlexistente com conteúdo diferente. Antes de renderizar, verifique otitle/nós do JSON, e dê preferência à entrada com o mesmo nome do produto. - 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 「」. - 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 umindex.htmlestático separado, e o botão de início deve alternar o alvo com base no protocolo. - 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áginasfile://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". - Em template strings, expressões regulares precisam de barra invertida dupla:
\d/\sem template strings degeneram parad/s, tornando a regex gerada na página inválida (o resumo já mostrou "0 etapas no total". Use\\d,\\sem scripts de página. - 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). - Fluxogramas com nomes duplicados no mesmo diretório têm cartões idênticos:
xxx - copia.htmlexxx.htmltê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). \nem template strings vira quebra de linha real: Em JavaScript de página escrito em templates comohomeScript(),'...\n\n' + xvira uma string literal com quebras de linha reais → erro de sintaxe no script da página (node --checkverificaserve.mjs, mas não detecta isso). Use\\n. Método de verificação: Extraia os dois<script>gerados porbuildHome()e executenode --checkem cada um (adicionei esta etapa de regressão).- 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]