fix: servidor — estado unificado, RCON, processos órfãos e reinício da depuração - #87
Merged
Merged
Conversation
A causa comum de #83, #84 e #86 era a mesma: `ServerController.term` — um terminal do editor — era a única fonte de verdade sobre 'há um servidor no ar'. Tudo que não passasse por aquele terminal ficava invisível: o servidor subido pelo depurador aparecia como parado, e um processo órfão não aparecia de forma alguma. Entra o ServerRegistry: a vida do servidor é sondada pela porta (opcode 'p' do protocolo de consulta, sem senha, com token aleatório que torna a resposta inequívoca — 10 ms), e a origem é declarada por quem inicia. Quando as duas discordam, a porta manda. Uma vigilância periódica mantém o painel honesto se o servidor cair sozinho ou subir por fora. Com isso: - O depurador declara a origem ao iniciar a sessão, e o painel passa a reconhecer o servidor (#84). - O tail do log segue o servidor, não quem o iniciou, então a saída do console volta a aparecer durante a depuração (#84). - start() detecta servidor já na porta e pergunta o que fazer, em vez de subir outro por cima (#86). - stop() só resolve quando a porta cala, e avisa se algo continuar respondendo — antes fechava o terminal e presumia que o processo tinha morrido junto (#86). - loadOmpConfig passa a ler rcon.enable; com o RCON desligado a extensão avisa e oferece ligá-lo, em vez de mandar pacotes para quem não escuta (#83). - Comando que executa sem devolver texto (gmx, players sem ninguém on-line) passa a confirmar o envio; antes o sucesso silencioso era idêntico à falha silenciosa (#83). Corrige também buildPacket, que montava os octetos com host.split('.') e mandava o pacote com IP errado quando o host era 'localhost' ou '::1'. Simplificações: setState e ServerRunState saem (a verdade agora é medida, não declarada); os dois laços de espera viram um só com a condição como parâmetro; a limpeza do tail, antes espalhada por quatro pontos, fica no listener do registry.
Reconhecer o servidor não bastava: os botões continuavam sem efeito quando a origem era a sessão de depuração, porque stop() só sabia encerrar pelo terminal — e ali não há terminal nenhum. Parar ficava esperando uma porta que nunca calaria. - **Parar** encerra a sessão de depuração (vscode.debug.stopDebugging), que é quem detém o processo filho. - **Reiniciar** avisa que o caminho é reiniciar a sessão: trazer o servidor de volta pelo painel o deixaria sem o depurador anexado, o que surpreende quem esperava continuar depurando. - **Iniciar** com uma sessão ativa informa que o servidor já está no ar, em vez de mostrar o aviso de porta ocupada — que é para órfão ou servidor externo, não para um servidor conhecido e legítimo.
O servidor da depuração é filho do processo do adaptador, que o mata no próprio Drop (crates/dap-adapter/src/main.rs) — no Linux há até um PR_SET_PDEATHSIG para o caso de o adaptador morrer abruptamente. A vida dos dois é a mesma por construção: não há como trocar o processo mantendo o depurador anexado, porque ele está preso ao processo antigo. Então reiniciar o servidor É reiniciar a sessão. O botão passa a encaminhar para workbench.action.debug.restart, que o editor já implementa e que reanexa o depurador preservando os breakpoints. Antes o botão só avisava, o que deixava o usuário sem saber o que fazer.
Três ajustes que a refatoração expôs: - `LogTailer.start` limpa o painel e recomeça a leitura. Como o tail passou a ser garantido em mais lugares, sem uma guarda o log seria apagado repetidamente e não daria para ler nada. `isTailing` responde se já está acompanhando aquele arquivo. - `revealLog` garante o tail antes de mostrar: abrir o log com o servidor no ar deve mostrar o log, não um painel vazio — o que aconteceria se o servidor tivesse subido pela depuração. - A vigilância só começa depois de carregar a configuração do projeto. Sem isso, um projeto com porta diferente de 7777 seria sondado na porta errada e reportado como parado com o servidor no ar. Atualiza também o comentário do restart, que descrevia a limitação do adaptador — ela deixou de existir.
Três defeitos no mesmo caminho, confirmados contra um open.mp 1.5.8 local e contra o fonte do servidor (Server/Components/LegacyNetwork/Query/query.cpp). O datagrama de resposta repete os 11 bytes do cabeçalho, seguidos do tamanho da mensagem (uint16 LE) e do texto — `BASE_QUERY_SIZE` é 11 e `handleConsoleMessage` escreve o tamanho antes do corpo. Lendo a partir do byte 11, os dois bytes de tamanho vinham colados no início de cada linha: em respostas curtas o segundo é 0x00, e era isso que chegava ao painel como dado inválido. Agora o campo delimita a mensagem. O comando ia em `latin1`, e o console do servidor fala UTF-8: `ação` saía como bytes inválidos e o eco voltava com U+FFFD. Vai em UTF-8; a senha continua em `latin1`, que é como o servidor a compara byte a byte. E o teto de linhas: o filtro de origem aceita qualquer remetente de loopback — que é o caso de uso normal —, então um processo local despejando datagramas renovaria o prazo para sempre e a promise nunca resolveria. `LogTailer.start` deixa de chamar `clear()`: o sink é compartilhado com a saída do RCON, e apagá-lo ali destruía o eco e a resposta já escritos. O `start` fixa `lastSize` no tamanho atual do arquivo, então não havia duplicação a limpar — e quem quer o painel vazio tem `clearOnStart`, que esta limpeza escondida atropelava.
O painel de comandos manda por RCON, cuja resposta só existe no canal de saída — mas `revealConsole` priorizava o terminal quando havia um, e o `start` o trazia à frente. Quem mandava um comando era levado ao painel que não mostra a resposta, e o eco parecia perdido. O terminal continua existindo para hospedar o processo e permitir pará-lo; deixa de ser o painel que se olha. O eco do comando passa a sair ANTES do envio. Depois que o `send` passou a juntar todos os datagramas, ele só resolve no silêncio que fecha a rajada, e nesse intervalo o tail já despejou as linhas — o comando aparecia embaixo do próprio resultado. E a resposta não é mais reescrita quando o tail está no ar: `ConsoleComponent::sendMessage` grava no log toda mensagem que devolve pelo console, então ela já vem por ali, com timestamp e nível. Repeti-la duplicava cada comando. O aviso de "executou sem saída" continua nos dois casos: não vem do servidor, e o log não o supre. Na sondagem, uma falha isolada é indistinguível de um datagrama perdido — `pingServer` manda um único datagrama, sem retransmissão. Passa a exigir três falhas seguidas, e só para quem já esteve no ar: sem nunca ter respondido não há nada a preservar, e insistir daria "no ar" para um servidor inexistente. `ServerStatus` ganha `respondeu`, verdadeiro só quando a porta respondeu nesta sondagem: `vivo` tolera perdas e serve à exibição, mas barrar uma ação do usuário exige resposta de fato — com a origem herdada, uma sessão de depuração encerrada travava o `start` seguinte.
O repack gravava as entradas com `zip.file` sem permissão, e gerava o pacote como DOS: o bit de execução se perdia, e o adaptador de depuração chegava à instalação com modo 644. O editor não conseguia lançá-lo e a depuração não iniciava, sem erro visível. Afetava qualquer instalação por VSIX em Linux e macOS. Precisa dos dois: `unixPermissions` na entrada e `platform: 'UNIX'` na geração — sem o segundo o JSZip ignora o modo. `notes/` sai do pacote: são rascunhos locais, já cobertos pelo .gitignore mas não pelo .vscodeignore, e estavam indo no VSIX.
`initialConfigurations` e `configurationSnippets` gravavam
`${workspaceFolder}/gamemodes/main.amx` — um palpite que só existe por
coincidência. Quem criava um launch.json ficava com uma configuração
apontando para um arquivo inexistente, e o F5 abortava calado. Passam a usar
`${fileDirname}/${fileBasenameNoExtension}.amx`, que o editor resolve a cada
launch para o `.amx` do arquivo aberto — o mesmo que `defaultProgram` já
fazia quando não havia launch.json.
E `ensureDebugBuild` deixa de retornar `false` em silêncio quando não há nem
source nem binário: diz qual caminho falhou e o que fazer. Mensagem
traduzida nos cinco idiomas.
Junto vai `bundle.l10n.pt-br.json`: o editor em pt-BR procura esse arquivo,
não o achava e registrava erro a cada ativação. O bundle padrão já é o PT
(chave e valor idênticos), então é cópia dele.
Contraparte do `pawnproRebuild` no adaptador: ele detecta que o `.pwn` está mais novo que o `.amx` e pede a recompilação; aqui ela acontece, porque o compilador e as flags são configuração do projeto, não conhecimento do adaptador. Compilado, o `restart` é reenviado e segue o caminho normal. Se a compilação falhar, o restart NÃO é reenviado: subir o servidor com o binário velho é justamente o que se quer evitar. `ensureDebugBuild` ganha `soSeMudou`, comparando as datas do fonte e do binário — no restart, recompilar um `.amx` já em dia só custaria tempo.
O servidor SA-MP e o open.mp legado são de 32 bits. Um plugin de 64 não carrega neles: o servidor recusa com "classe ELF errada" no meio de dezenas de linhas de boot, e a depuração falha sem nada aparecer no editor — o `/teste` executa, o breakpoint é ignorado, e nada explica por quê. O preflight passava porque só olhava presença, marcador e registro. Agora compara a arquitetura do plugin com a do executável do servidor, lendo o cabeçalho ELF (byte 4 = classe) ou PE (campo `Machine`). Sem parser: são poucos bytes em deslocamento fixo. Só reporta quando as duas são conhecidas e diferentes — sem o executável, ou com formato que não sabemos ler, o silêncio é melhor que um alarme falso. Parte de #85. A verificação de VERSÃO do plugin, que a issue também pede, continua em aberto.
O exemplo abaixo do campo sai da primeira sonda que o padrão aceita, e as sondas eram fixas: os cinco estilos embutidos mais o nome-base com o prefixo literal do padrão. Padrões válidos e plausíveis não casavam com nenhuma, e o campo ficava sem exemplo — o usuário sem confirmação de que o padrão faz o que espera, e podendo concluir que está errado quando não está. Três lacunas, as três da issue: - O nome-base era só camelCase, então /^g_[A-Z].../ não casava. Agora as sondas incluem a base capitalizada e a base toda minúscula, que também cobre /^m_[a-z]+$/. - `literalPrefix` lia apenas literal contíguo: com alternância no início (/^(g|s)_.../) devolvia vazio e nenhuma sonda ganhava prefixo. Virou `literalPrefixes`, que devolve um prefixo por ramo, com o literal que segue o grupo (o `_` de (g|s)_ pertence aos dois). - /^p[A-Z].../ passa a casar pela base capitalizada. Os seis padrões da issue produzem exemplo. O custo não muda: são 11 sondas no caso comum (eram 7), e o orçamento de 50 ms de `regexSample` é compartilhado entre elas — mais sondas não esticam o teto. Fecha #82.
O campo ficava vermelho com "Nenhum dos exemplos passa neste padrão" quando o padrão não casava com nenhuma sonda. Mas as sondas são nomes que a página inventa para ILUSTRAR o padrão, não uma definição do que é válido: um padrão correto que use outro prefixo ou outra convenção não casa com nenhuma delas e não tem defeito algum. A validação acusava o usuário de um problema que é da lista de sondas. Fica só o erro de forma: falta de barras e regex que não compila. Junto, o preview ganha "Ver exemplos aceitos (N)" quando o padrão aceita mais de um nome, abrindo a lista completa num modal com rolagem — `mostrarExemplosDoPadrao(category, raw)` recebe o padrão e monta a lista dele. E corrige `T`, que nunca foi definido: as traduções vivem em `_i18n`. O erro existia nas mensagens de validação (só disparava com padrão inválido) e passou a derrubar a renderização de todas as categorias quando o preview começou a usar a mesma referência.
A caixa de exemplos mostra um nome por critério, e um padrão costuma aceitar muitos — /^(g|s)_.../ aceita nomes com s_ e nada revelava isso. Abaixo do preview aparece "Ver exemplos aceitos (N)", que abre a lista inteira. `mostrarExemplosDoPadrao(category, raw)` recebe o padrão e monta a lista dele: o modal não sabe de onde veio o conteúdo. Teto de 300 exemplos — a rolagem dá conta do volume, mas uma lista sem fim não ajuda a entender o padrão. A busca reaproveita o input dos favoritos, e a rolagem usa o mesmo estilo da lista de lá. A contagem fica na faixa da busca, não ao lado do padrão: ali ela mudava de largura a cada filtragem e empurrava o bloco do regex. Largura de 7ch, que é o maior valor possível (300/300), pela mesma razão. Um padrão longo quebra em linhas em vez de ser cortado, e o recuo do modal acompanha a largura da janela como o da página, em escala menor. Junto, duas gambiarras saem do gerador de sondas: o sublinhado era um laço à parte no fim, repetindo o que o laço dos prefixos já fazia, e o teto de 12 caracteres do prefixo aparecia como número solto em dois lugares — virou MAX_PREFIXO, com o motivo documentado.
A página recusava padrões acima de 200 caracteres, alegando expressão inválida — e o `readAcceptedStyles` os descartava, então o campo era limpo sozinho ao sair. Um padrão longo e correto sumia sem explicação. Medindo, o limite protegia a coisa errada: um padrão de 244 caracteres, benigno, custa 0 ms para 60 sondas; `/^(a+)+$/`, com sete, leva 27 SEGUNDOS numa única sonda. O risco é o backtracking, e vem da forma, não do tamanho. O orçamento de 50 ms não cobria: é conferido ANTES de cada teste, então quando a página volta a responder já travou. Passa a barrar por estrutura — grupo quantificado cujo interior também quantifica ou alterna, que é a forma dos padrões catastróficos. Barra (a+)+, (a*)*, (a|aa)+, ([a-z]+)+; deixa passar os legítimos, inclusive os longos com alternância, que antes ficavam sem exemplo. `compileRule` volta a responder só "compila?", sem o teto: era ele que confundia validade com custo, e por isso o padrão não era salvo. Junto, os erros do campo passam a aparecer em TEXTO, não só na borda com tooltip — sem passar o mouse não havia como saber o que estava errado. A mensagem fica na coluna dos rótulos: na dos controles ela dividia espaço com a grade de estilos e empurrava os botões.
A descrição do grupo trazia "Vazio = sem checagem" — uma nota genérica no cabeçalho, lida uma vez e esquecida, enquanto a categoria sem critério ficava com o espaço do exemplo em branco, sem explicar por quê. No lugar, cada categoria sem estilo nem padrão mostra "Nenhuma regra selecionada" com um ícone, no mesmo formato do estado vazio da lista de favoritos. A informação aparece onde e quando importa.
Parar fechava o terminal, confirmava pela porta e, se ela continuasse ocupada, pedia ao usuário que "verificasse antes de iniciar outro" — sem dizer o quê. O processo que sobrevive ao terminal fica escutando a mesma porta, e o painel e o RCON passam a responder por um servidor que já não é o do projeto: foi o que tornou o diagnóstico de #83 não-determinístico. Agora a extensão identifica quem ocupa a porta (`lsof -t`, com `fuser` de reserva) e oferece encerrá-lo, com SIGTERM antes de SIGKILL. Sem ferramenta disponível, ou no Windows, mantém o aviso de antes — a lista vazia significa "não sei", não "não há". Parte de #86. As outras duas frentes da issue já estavam cobertas: a porta é checada antes de iniciar, e a vigilância na ativação detecta um servidor que já estava no ar.
Três frentes de limpeza.
**Idioma dos identificadores.** O `core` já era inteiramente em inglês, e as
três exceções em `editor/server.ts` (`enderecoAtual`, `garantirTail`,
`statusAtual`) são desvios — 3 de 14 métodos do arquivo. Nada em commit,
documentação ou histórico dizia o contrário, então o que criei nesta branch
passa a seguir a prática: `pidsOnPort`, `killProcess`, `PAWNPRO_DIR`,
`riskyForPreview`, `MAX_EXAMPLES`. A regra entra no CONTRIBUTING e no
CLAUDE.md, com o aviso de que os desvios não são exemplo a seguir.
**Plural de verdade.** Cinco mensagens usavam "{0} item(ns) movido(s)", que
não é português. Cada uma passa a ter as duas formas, e um helper `plural`
escolhe pela contagem — a alternativa era repetir o ternário em cada chamador.
**Duplicação e silêncio.** O literal `.pawnpro` estava em oito lugares (virou
`PAWNPRO_DIR`), o caminho do backup de nomenclatura era montado duas vezes com
o mesmo carimbo, e três `catch {}` engoliam erro sem dizer por quê.
A padronização anterior alcançou funções, constantes e tipos, mas deixou as variáveis locais que eu havia escrito em português — `linhas`, `vazio`, `conta`, `busca`, `alvo`, `ramo`, `resto`, entre outras. Metade da regra aplicada é pior que nenhuma: quem lesse o arquivo veria os dois idiomas lado a lado sem critério aparente. Os identificadores em português que restam no `editor/` são anteriores a esta branch e ficam onde estão.
Fecha a padronização: o filtro de comandos sensíveis do painel ainda tinha `COMANDOS_SENSIVEIS`, `ROTULOS_DE_SEGREDO`, `pareceSegredo` e as variáveis do laço. Todos locais ao arquivo. As palavras em português dentro da regex e dos comentários continuam — são texto, não identificador. E o `notes/**` acrescentado nesta branch estava solto no fim do .vscodeignore, depois da observação sobre l10n/, como se tivesse relação com ela. Vai para a seção de documentação interna, junto de docs/ e changelogs/.
O estilo e a lógica viviam dentro de template literals no TypeScript: 39 KB de
CSS e 28 KB de JS como string, sem realce, sem validação e com as armadilhas
do template — crase quebra a compilação, barra invertida precisa ser dobrada.
O fonte passa a viver em `assets-src/`, declarado em `assets.manifest.json` no
mesmo formato que o projeto já usa em outro repositório, e o esbuild o
minifica para `out/assets/`, de onde a extensão o serve. Nada é descoberto por
varredura: uma folha só entra no pacote se estiver declarada, e o build falha
se o arquivo sumir.
CSS 39,0 KB → 23,4 KB (−40%)
JS 28,4 KB → 12,9 KB (−55%)
bundle da extensão: 1173 KB → 1129 KB
`npm run bundle:dev` gera sem minificar e com source map — no minificado as
ferramentas de desenvolvedor da WebView não ajudam.
A settingsView ganha `nonce` no CSP em lugar de `script-src 'unsafe-inline'`:
com nonce, só os scripts que a extensão gerou executam. As duas interpolações
do script viraram módulo próprio (a animação da marca, ainda usada pela
storeView) e um `data-max-examples` no body.
Vai junto a correção da página de Ajuda, que toca os mesmos arquivos: o
rótulo do adaptador citava `dap-adapter`, que é o crate, não o repositório
(`pawnpro-debugger`); e o texto do compilador afirmava versões que não
conferiam — o pacote do open.mp distribui a 3.10.10, não a 3.10.11. A lista do
que foi verificado vai para docs/compilers.md, com colunas por sistema, e a
Ajuda ganha o link.
A storeView fica de fora — está em trabalho.
…ngsView
Fecha a migração das WebViews: o painel do servidor era a última com script
embutido. As sete interpolações eram todas dados — seis ícones SVG e o objeto
de traduções — e passam por um `<script type="application/json">`, que o CSP
permite sem nonce por não ser executável. Os SVGs continuam definidos uma
única vez no TypeScript, servindo tanto o HTML quanto o script.
JS 35,9 KB → 18,0 KB (−50%)
A settingsView era a única das cinco WebViews SEM Content-Security-Policy —
justamente a que recebe mais entrada do usuário. Agora tem, e já no modelo
restrito que o script externo permitiu: `script-src 'nonce-...'` em vez de
'unsafe-inline'. O 'unsafe-inline' permanece só em style-src, por causa do
<style> com a cor de destaque.
E a página de compiladores afirmava build para macOS sem verificação: os
workflows do compilador do open.mp publicam apenas Windows e Linux.
As cinco mensagens já existiam no nls.ts — "Iniciando servidor...", "Servidor iniciado", "Parando servidor...", "Servidor parado", "Reiniciando servidor..." — e nenhuma era usada. Subir ou parar leva segundos, e a única pista era o painel mudar de estado quando a porta respondia. Agora cada etapa mostra a notificação de progresso enquanto espera a porta, e confirma ao fim. O reinício anuncia o ciclo uma vez, não as duas metades: com `restarting` ligado, a parada não avisa e quem fecha é o "servidor iniciado". Junto, a limpeza que a revisão encontrou: - quatro mensagens órfãs no nls.ts (`detecting`, `noCompiler`, `notDetected`, `notFound`), restos de refatoração, e suas traduções nos cinco bundles; - o build dos assets compilava em série, sem razão — os arquivos são independentes; - e não detectava duas entradas com o mesmo destino, que se sobrescreveriam em silêncio deixando o pacote sem um dos arquivos.
O `stop` escolhia como parar a partir de `registry.origem === 'debug'`, um estado que caduca sozinho: `status()` zera a origem após três sondagens UDP perdidas (~12 s), e o servidor parado num breakpoint deixa de responder ao datagrama. Quando caducava, o ramo do `stopDebugging` era pulado, nenhum outro rodava, e a parada terminava sem pedir nada a ninguém — o servidor seguia vivo e era acusado de órfão na porta. O critério passa a ser a sessão que o editor entrega. `ServerController` guarda a `DebugSession` viva pelos eventos de início e fim, comparando o `id` ao limpar para não apagar o registro de outra sessão. Com isso o valor `'debug'` de `ServerOrigin` ficou sem leitor e saiu, junto do contorno que existia só para compensá-lo (a guarda do `start` usava `respondeu` em vez de `vivo`). Outros defeitos corrigidos no mesmo caminho: - `start` resolvia antes de o servidor subir: o laço de espera era `void`, e o `restart` dava o ciclo por concluído com a espera ainda correndo. - `restarting` era campo e vazava entre operações — os `return` de guarda do `start` não passavam pelo `finally` que o zerava. Vira parâmetro de `start`/`stop`. - `stop` devolvia `void`, e o `restart` seguia para o `start` mesmo com a porta ainda ocupada, gerando duas mensagens sobre o mesmo processo. Agora devolve se parou, e o `restart` interrompe o ciclo. - `restart` repetia a guarda de porta que o `stop` já faz, sondando duas vezes para dar a mesma resposta. - A tolerância do registry mantinha `vivo` por ~12 s após a parada: `stop` invalida o estado com `markStopped()` no lugar da sondagem, evitando um `onChange` com o estado errado seguido da correção. Mensagens que faltavam: confirmação e progresso ao reiniciar em depuração (o clique não dava retorno nenhum), "reiniciado" em vez de "iniciado" ao fim de um reinício, e aviso quando o prazo de subida esgota — antes o start terminava em silêncio com o painel dizendo "parado" sem explicar por quê. Remove a tradução órfã "Encerrar e iniciar de novo", do fluxo de duas perguntas que o `resolvePortConflict` unificou, e traduz `portBusyOther`, que era usada no código e não estava em nenhum bundle. Identificadores em português passam a inglês em `server.ts`, `serverRegistry.ts` e nos JS dos painéis, incluindo os três desvios que o CLAUDE.md registrava como preexistentes. Documenta os fluxos dos dois painéis em docs/server-panel-flow.md, com os princípios que os regem e a lacuna conhecida: `prepareServer` faz preflight do plugin mas não checa a porta antes do F5.
…só pediu O `prepareServer` fazia o preflight do plugin mas não olhava quem já estava na porta: com um zumbi ali, o F5 subia um servidor que disputava o mesmo datagrama e o diagnóstico virava não-determinístico. O `ensurePortFree` roda entre o preflight e a gravação do `serverCommand`, com o mesmo filtro de dono do painel — só oferece encerrar o que é comprovadamente o executável do projeto. Relendo o painel para desenhar o fluxograma, apareceram seis defeitos: - `start` resolvia antes de o servidor subir: o laço de espera era `void`, e o `restart` dava o ciclo por concluído com a espera ainda correndo. - `onDidCloseTerminal` era registrado a cada `start` e só se descartava se aquele terminal fechasse — o de cada início anterior ficava pendurado. Passa a ser um só, com o ciclo de vida do controlador. - `stop` não tinha ramo para o terceiro dono possível (um servidor do projeto iniciado por fora): nenhum pedido de parada era feito, e o prazo inteiro se esgotava antes de oferecer encerrar. - Encerrar podia falhar em silêncio: quando todos os kills reportavam sucesso mas a porta continuava ocupada, nada era dito. - `isLoopbackHost` tratava `0.0.0.0` como loopback. É o curinga "todas as interfaces", e a função decide se a senha do RCON pode sair em texto claro. Octetos acima de 255 também passavam. - O contexto `pawnpro.server.ours` era gravado por respostas concorrentes de uma consulta que bloqueia ~200 ms na thread da extensão; a última a resolver vencia, mesmo sendo a mais antiga. Mensagens que faltavam: progresso e confirmação ao reiniciar em depuração, "reiniciado" em vez de "iniciado" ao fim de um reinício, e aviso quando o prazo de subida esgota — antes o clique terminava em silêncio com o painel dizendo "parado" sem explicar por quê. Os controles nativos do depurador (barra flutuante, F5, Shift+F5) não passam pelo painel e não mostravam nada: um `DebugAdapterTracker` acompanha o ciclo por eles, com uma barra só que troca de título entre compilar e reiniciar. Acrescenta `npm test` com 10 casos sobre `isLoopbackHost` e `isProjectServer`, as duas funções que decidem se a senha sai em texto claro e se um processo pode ser encerrado. Roda no `node --test` sem dependência nova. `isProjectServer` e `pidsOnPort` passam a funcionar em macOS (`ps`) e Windows (`netstat` e `Get-Process`); antes só o Linux tinha inspeção real, e o Windows afirmava "ocupada por outro programa" mesmo para um zumbi do próprio projeto. `killProcess` no Windows usa `taskkill` sem `/F` primeiro: `process.kill` com SIGTERM ali encerra na hora e o servidor não salva nada. Documenta os fluxos dos dois painéis em docs/server-panel-flow.md.
NullSablex
force-pushed
the
fix/servidor-estado-unificado
branch
from
September 3, 2026 10:39
570691b to
950e81c
Compare
O `setIcon` recebia a string do SVG e a parseava com `DOMParser` para inserir
os nós. O CodeQL sinaliza isso como "DOM text reinterpreted as HTML", e tem
razão quanto à forma: parsear markup em runtime é o padrão que a regra procura,
independentemente de a origem ser confiável.
Os ícones passam a ser dados — `{ d, attrs }` por `<path>` — e a WebView monta
os nós com `createElementNS`. Não há mais markup a parsear, e o alerta some por
construção em vez de por exceção.
Para os pontos em que a extensão serve o HTML pronto, `iconMarkup()` produz a
mesma string de antes a partir dos mesmos dados: uma definição só, nas duas
saídas.
NullSablex
marked this pull request as ready for review
September 3, 2026 11:22
This was referenced Sep 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
O que muda
Unifica o estado do servidor, corrige as falhas silenciosas do RCON e o reinício da depuração. Fecha #82, #83, #84, #85 e #86.
Este é o último patch da linha 3.5 — a 3.5.1. A próxima versão é a 4.0.0, com mudanças profundas na extensão e em todas as funcionalidades, sem prejuízo para quem já usa: a configuração e os projetos existentes seguem valendo.
A causa comum
ServerController.term— um terminal do editor — era a única fonte de verdade sobre "há um servidor no ar". Tudo que não passasse por aquele terminal ficava invisível: o servidor da depuração aparecia como parado, um processo órfão não aparecia de forma alguma, e o RCON mandava pacotes sem saber se havia alguém do outro lado.Corrigir cada sintoma isoladamente deixaria o desenho igual, e o problema voltaria pela próxima porta.
O desenho
Entra o
ServerRegistry:pdo protocolo de consulta — não exige senha e leva um token aleatório de 4 bytes que o servidor devolve, o que torna a resposta inequívoca.O terminal deixa de ser como saber e passa a ser apenas como parar.
Mas a origem registrada expira sozinha depois de algumas sondagens perdidas — e um servidor pausado num breakpoint deixa de responder ao datagrama. Por isso ela serve para exibir, nunca para decidir como parar: esse critério é a sessão que o editor entrega, que é um fato. Era essa distinção que faltava, e sem ela parar a depuração pelo painel acusava um órfão que na verdade continuava vivo.
Correções
rcon.enablepassa a ser lido; um comando que executa sem devolver texto confirma o envio em vez de deixar a linha muda; ebuildPacketdeixa de montar o cabeçalho com IP errado quando o host não é IPv4 numéricostart()detecta servidor já na porta e pergunta o que fazer;stop()só resolve quando a porta calaAlém disso, oito defeitos encontrados relendo o painel para desenhar o fluxograma:
startresolvendo antes de o servidor subir, um listener de terminal vazando a cada início,stopsem ramo para o servidor iniciado por fora, encerramento falhando em silêncio, e0.0.0.0tratado como loopback — o que fazia a senha do RCON, que trafega em texto claro, sair da máquina.Fora do Linux
A inspeção de portas e processos só funcionava no Linux. No macOS o filtro reprovava tudo e o painel nunca oferecia encerrar; no Windows a extensão afirmava "ocupada por outro programa" mesmo para um servidor do próprio projeto. Ambos passam a funcionar, e no Windows o encerramento pede a saída limpa antes de forçar.
Feedback visual
Iniciar, parar e reiniciar levam segundos e não davam retorno: o painel só mudava quando a porta respondia. As três passam a mostrar progresso e confirmar ao terminar. Os controles nativos do depurador também, com uma barra única que troca de texto entre compilar e reiniciar.
Testes
npm testcom 10 casos sobreisLoopbackHosteisProjectServer— as duas funções que decidem se a senha sai em texto claro e se um processo pode ser encerrado. Roda nonode --test, sem dependência nova.Depende de
PawnPro-Debugger v0.2.1 — o progresso dos controles nativos usa eventos que só existem a partir dela. Os checksums já estão pinados no
package.json.