Skip to content

fix: servidor — estado unificado, RCON, processos órfãos e reinício da depuração - #87

Merged
NullSablex merged 26 commits into
masterfrom
fix/servidor-estado-unificado
Sep 3, 2026
Merged

fix: servidor — estado unificado, RCON, processos órfãos e reinício da depuração#87
NullSablex merged 26 commits into
masterfrom
fix/servidor-estado-unificado

Conversation

@NullSablex

@NullSablex NullSablex commented Sep 2, 2026

Copy link
Copy Markdown
Owner

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:

  • A vida vem da porta. Sondagem com o opcode p do 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.
  • A origem é declarada por quem inicia.
  • Quando as duas discordam, a porta manda. Um terminal aberto com o processo morto não conta como servidor no ar.
  • Uma vigilância periódica mantém o painel honesto se o servidor cair sozinho ou subir por fora.

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

#82 pré-visualização do padrão próprio cobre mais padrões válidos, deixa de travar com padrão catastrófico e diz quando a categoria não tem regra
#83 rcon.enable passa a ser lido; um comando que executa sem devolver texto confirma o envio em vez de deixar a linha muda; e buildPacket deixa de montar o cabeçalho com IP errado quando o host não é IPv4 numérico
#84 o painel reconhece o servidor da depuração, e o tail do log passa a seguir o servidor em vez de quem o iniciou
#85 aviso quando o plugin de depuração é de outra arquitetura — antes o servidor o recusava no boot e o erro se perdia entre as linhas de carga
#86 start() detecta servidor já na porta e pergunta o que fazer; stop() só resolve quando a porta cala

Além disso, oito defeitos encontrados relendo o painel para desenhar o fluxograma: start resolvendo antes de o servidor subir, um listener de terminal vazando a cada início, stop sem ramo para o servidor iniciado por fora, encerramento falhando em silêncio, e 0.0.0.0 tratado 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 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.

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.

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.
@github-actions github-actions Bot added i18n Traduções e localização (l10n, package.nls) area: editor Camada de adaptação em src/editor area: core Lógica pura em src/core labels Sep 2, 2026
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.
@github-actions github-actions Bot added deps Atualizações de dependências area: engine Integração com o motor LSP / debugger build Scripts de build, bundle e empacotamento labels Sep 2, 2026
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.
@github-actions github-actions Bot added the docs Documentação label Sep 3, 2026
Comment thread assets-src/js/server.js Fixed
Comment thread assets-src/js/server.js Fixed
Comment thread assets-src/js/server.js Fixed
Comment thread assets-src/js/settings.js Fixed
Comment thread assets-src/js/settings.js Fixed
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
NullSablex force-pushed the fix/servidor-estado-unificado branch from 570691b to 950e81c Compare September 3, 2026 10:39
Comment thread assets-src/js/server.js Fixed
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 NullSablex changed the title fix: servidor — estado unificado, RCON e processos órfãos fix: servidor — estado unificado, RCON, processos órfãos e reinício da depuração Sep 3, 2026
@NullSablex
NullSablex marked this pull request as ready for review September 3, 2026 11:22
@NullSablex
NullSablex merged commit 0a9d868 into master Sep 3, 2026
4 checks passed
@NullSablex
NullSablex deleted the fix/servidor-estado-unificado branch September 3, 2026 11:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: core Lógica pura em src/core area: editor Camada de adaptação em src/editor area: engine Integração com o motor LSP / debugger build Scripts de build, bundle e empacotamento deps Atualizações de dependências docs Documentação i18n Traduções e localização (l10n, package.nls)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants