Voltar ao Blog
updaterdevtoolsrefactorcommunitymilestone

Auto-Update, um Inspector ao Vivo e o Compilador que se Cortou pela Metade

This dated article records the Perry version and measurements available when it was published. Use the current limitations, platform guide, and benchmark artifact for decisions today.

O último post fechou em v0.5.306 com a história gen-GC + JSON + benchmarks. Quatro dias depois, Perry está em v0.5.359 — são 53 patch releases — e a história é outra de novo. Nenhuma dessas releases é uma manchete de números de benchmark. Quase todas são issues do tracker sendo fechadas.

  • perry/updater chega — auto-update no estilo Sparkle/Tauri para apps desktop (Ed25519 sobre um digest SHA-256, sentinel-rollback, relançamento desacoplado). PR da comunidade por TheHypnoo (#224).
  • Geisterhand Fase D — um inspector ao vivo em http://localhost:7676 com árvore de widgets, detalhe por widget, dispatch de cliques e edição de estilo ao vivo via POST /style/:h.
  • O refactor do compilador. Entre v0.5.329 → v0.5.343 os quatro arquivos mais citados foram fatiados: lower::lower_expr 6.687 → 624 LOC (−91%), compile.rs 9.391 → 3.783 LOC (−60%), lower.rs 13.591 → 7.554 LOC (−44%), lower_call.rs 7.000+ → 4.681 LOC (−33%). O novo walker.rs transforma a classe de bug do catch-all _ => em erro de compilação.
  • O styling UI Fase C fecha — props inline style: { ... } em todo widget no Apple, Android, GTK4, Windows e Web. Windows ganha 4 de 5 stubs ligados (decoration / opacity / borders); só falta widget.shadow (follow-up com DirectComposition).
  • Um bucket Scoop para Windows: scoop install perry-ts/perry. Sidecars SHA-256 no workflow de release.
  • Onda de fixes de issues da comunidade — cerca de 30 issues fechadas em runtime, codegen, fetch, GTK4, linker do Windows, async e stdlib.

1. perry/updater — auto-update para apps desktop

Antes do fix, Perry não tinha caminho de update. Apps eram lançadas, e pronto. TheHypnoo abriu #224 com a história completa:

import { initUpdater, checkForUpdate, markHealthy } from "@perry/updater";

initUpdater(); // sentinel-rollback se o lançamento anterior crashou

const update = await checkForUpdate({
  manifestUrl: "https://example.com/updates/manifest.json",
  publicKey: "<ed25519 raw 32-byte hex>",
  currentVersion: "1.4.0",
});

if (update) {
  await update.download((pct) => console.log(`${pct}%`));
  await update.installAndRelaunch();
}

markHealthy(); // chamar depois de a nova build subir corretamente

Modelo de confiança: Ed25519 sobre o digest SHA-256 do arquivo (não sobre os bytes do arquivo — mantém a verificação barata em binários grandes). O manifest é JSON, versionado por esquema, uma entrada por tripla <os>-<arch>. Instalação atômica com backup <exe>.prev, relançamento desacoplado (setsid em Unix, DETACHED_PROCESS no Windows). Mobile fica de fora por design — App Store / Play Store controlam o pipeline de instalação no nível do SO.

Duas peculiaridades do runtime de Perry apareceram ao escrever o smoke test, e foram corrigidas no caminho:

  • response.arrayBuffer() retornava um stub só com metadata. Corrigido em #232 (também TheHypnoo) — js_response_array_buffer agora aloca um BufferHeader real e faz memcpy de resp.body dentro.
  • fs.appendFileSync escrevia 0 bytes. Corrigido em #226 — o caminho de lowering do namespace-import (import * as fs from "fs") não tinha braço para appendFileSync, e o codegen LLVM também não tinha braço para a variante HIR. Ambos ligados.

A documentação vive em docs/src/updater/overview.md.

2. Geisterhand: inspector ao vivo em localhost:7676

Geisterhand era o harness de testes de UI in-process do Perry — uma API HTTP na porta 7676 para snapshotear estado de widget e dispatchar cliques. A Fase D o transforma em um inspector estilo devtools que dá para abrir em qualquer navegador.

  • Passo 1 (v0.5.349)GET / serve uma UI vanilla-JS de página única com árvore de widgets, detalhe por widget (frame, value, raw JSON), auto-refresh de 1,5 s com pause/resume, e um botão «disparar onClick». O codegen prende INSPECTOR_HTML contra o lazy-load -dead_strip do macOS para sobreviver às release builds.
  • Passo 2 (v0.5.350)POST /style/:h recebe um saco de props JSON e aplica ao vivo. 9 props (backgroundColor, color, borderColor, borderWidth, borderRadius, opacity, padding, hidden, enabled) fluem da thread HTTP → thread principal via a pump-queue existente. JSON ruim → 400; handle ruim → 400; props desconhecidas são filtradas no servidor e a response lista quais passaram.
perry compile main.ts -o app --enable-geisterhand
./app &
open http://localhost:7676
curl -X POST localhost:7676/style/3 \
  -H 'content-type: application/json' \
  -d '{"backgroundColor":"#1a1a1e","opacity":0.8}'
# => {"ok":true,"applied":["backgroundColor","opacity"]}

O dispatcher do macOS está ligado; Linux / Windows / iOS / tvOS / visionOS / Android seguem o mesmo formato e são os próximos.

3. O refactor do compilador — fatiando os quatro maiores arquivos

Cinco issues no tracker (#167, #169, #212, #214, mais uma cauda longa) tinham a mesma forma: uma nova variante de Expr adicionada a ir.rs, mas um dos quatro walkers ad-hoc em lower.rs tinha um catch-all _ => e mis-compilava silenciosamente a nova variante. Pegar isso em runtime é caro — às vezes invisível, às vezes um SIGSEGV sob SSO.

v0.5.329 introduziu crates/perry-hir/src/walker.rs com walk_expr_children / walk_expr_children_mut — matches exaustivos sobre todas as 178 variantes de Expr, sem catch-all. Adicionar uma nova variante sem listá-la aqui é agora um erro de compilação. Os quatro consumidores (substitute_locals, find_max_local_id::check_expr, collect_local_refs_expr, remap_local_ids_in_expr) colapsaram:

FunçãoAntesDepoisΔ
find_max_local_id::check_expr22557−75%
substitute_locals55380−86%
collect_local_refs_expr72070−90%
remap_local_ids_in_expr54285−84%

Total: −1.830 linhas de descent duplicado, substituídas por +1.840 linhas de um walker centralizado — saldo zero, mas a classe de bug se foi.

Isso destravou o resto. v0.5.331 → v0.5.343 partiram os quatro monólitos em 14 commits. Os números de capa:

ArquivoAntesDepoisΔ
lower::lower_expr6.687624−91%
compile.rs9.3913.783−60%
lower.rs13.5917.554−44%
lower_call.rs7.000+4.681−33%

O split aterrissou como 19 novos sub-módulos focados: compile/{parse_cache, strip_dedup, library_search, object_cache, resolve, collect_modules, optimized_libs, targets, link}.rs, lower/{expr_misc, expr_function, expr_object, expr_call, expr_member, expr_assign, expr_new}.rs, lower_call/{ui_styling, builtin, native}.rs, mais um novo crate crates/perry-dispatch que virou a única fonte de verdade para tabelas de método UI / system / i18n (o fan-out _ => "perry_ui_unknown" que levava às surpresas «compila no macOS, quebra na web» do issue #191 agora é um único lookup).

Ganhos de perf de Tier 4 vieram juntos (v0.5.335–v0.5.336):

  • Fundidos dois passes em inline_functions e três passes rayon em compile.rs — economiza 5 scans de módulo + 3 round-trips do scheduler por compilação.
  • Limitado o parse cache do perry dev a 500 entradas, eviction FIFO. Antes do fix, uma sessão andando por node_modules podia segurar mais de 100 MB de AST do SWC.
  • Paralelizado o loop de escrita .ll pós-codegen — wall-time 2–4× mais rápido em SSDs com 50+ módulos.
  • Arc<I18nTable> em vez de clonar a tabela de locales por worker.

Os testes do workspace ficaram em 434 passed / 0 failed / 5 ignored em todo commit; gap tests na baseline 25/28; doc-tests na baseline 80/82.

4. UI styling Fase C, encerrada

A Fase C era o rollout de style: { ... } inline. Os passos 1–7 fecharam nesta janela:

  • v0.5.305 → v0.5.306 — superfície de tipo StyleProps + style: inline em Button.
  • v0.5.307 → v0.5.309 — destructure inline color/padding/shadow em todo widget de tabela, depois VStack / HStack.
  • v0.5.310 → v0.5.311 — strings hex + gradient + parseColor em runtime para valores dinâmicos.
  • v0.5.312 — docs de styling + issue de tracking do Windows.

Depois a varredura cross-platform:

  • GTK4 (#202, #206) — 4 FFIs de styling ligados, mais 7 FFIs faltantes que travavam o gate dos doc-tests do Linux (v0.5.322).
  • macOS (v0.5.324) — encanamento de sombra CALayer para widget.shadow + infraestrutura de visual_test; class-probe de set_color para widgets que não são NSTextField.
  • iOS / tvOS / visionOS (v0.5.346) — Button com color: ... batia em setTextColor: em UIButton, que não implementa esse seletor; o panic do objc2 cruzava uma fronteira extern "C" e o processo abortava. Corrigido com o mesmo padrão de class-probe do macOS — UIButton agora vai por setTitleColor:forState:UIControlStateNormal.
  • Windows (v0.5.347) — 4 de 5 stubs de styling ligados (text.decoration via round-trip LOGFONT, widget.opacity via WS_EX_LAYERED + SetLayeredWindowAttributes, borders via SetWindowSubclass + WM_PAINT). Só falta widget.shadow (precisa de DirectComposition).

A matriz de styling em docs/src/ui/styling-matrix.md termina a janela com Web em 43/43 Wired, Windows em 42/43 Wired, o resto em cobertura completa.

5. A passada de correção do runtime — issue por issue

Um tema do período: cada miscompile que entrou pelo tracker virou ou um fix ou um erro de compilação. Destaques:

  • #212 (v0.5.323) — métodos de classe dentro de fn não conseguiam capturar locais da fn que envolvia. Repros multi-módulo agora batem com Node byte por byte.
  • #214 (v0.5.321 + v0.5.330) — unboxing de string-handle SSO-safe em 7 sites com operandos string: arr.join, arr.toString, obj[stringKey] get/set/delete, string.match(re), process.env[dynKey], input de digest crypto. Antes do fix, cada um ou retornava lixo silenciosamente ou dava SIGSEGV em operandos string inline.
  • #221 (v0.5.351) — arrays vazios const em nível de módulo perdiam as escritas arr[i]= de dentro de funções. Apareceu quando discoverLevels() de Bloom-Engine/jump populava LEVEL_FILES em nível de módulo via index-assign e a tela de seleção de fase saía vazia.
  • #233 (v0.5.357)Array.push de dentro de função async era silenciosamente limitado a 16 elementos quando o array entrava como parâmetro. Funções async não são inlined; a realocação retornava um novo ponteiro que o chamador nunca via. Fix: instalar um ponteiro de forwarding na posição antiga a cada crescimento, reusando o mecanismo GC_FLAG_FORWARDED existente do GC.
  • #235 (v0.5.358) — o dispatch de parâmetros default de método passava lixo quando os chamadores omitiam args do final. Duas partes contribuintes: declares de método cross-module hardcodavam 6 doubles em vez de arity + 1, e lower_class_method não chamava build_default_param_stmts. Apareceu em findOne(filter, options = {}) de mongodb travando em silêncio; o fix é uniforme entre dispatch local e cross-module.
  • #236 (v0.5.355) — três bugs independentes de fetch + promise vindos de um único repro: api.github.com retornava 403 anônimo (User-Agent default agora setado), .then(console.log) travava para sempre (callbacks null não empurravam entradas para a TASK_QUEUE), todo rejeito de fetch imprimia Uncaught exception: [object Object] (*StringHeader nu NaN-boxado em vez de um ErrorHeader real).
  • #234 (v0.5.359)Blob real com métodos de instância arrayBuffer / text / bytes / slice. Antes do fix, await response.blob() retornava um stub só com metadata {size, type}. Fix em três partes aterrissou em runtime + HIR + codegen.

Mais os pequenos atrasos:

  • #181 — strip-dedup podava em excesso monomorfizações genéricas no Linux + silent-fallback do link do GTK4. Fix: substituir filtragem por padrão de nome por comparação de conjunto de símbolos via llvm-nm. Membros com mesmo um único símbolo único são mantidos. libperry_ui_macos.a reduzido de 196 → 35 objetos sem erros de link.
  • #220secur32.lib adicionado à linha de link do Windows.
  • #198 — i18n FormatNumber round-trip de FP via Ryū.
  • #188 — codegen dispatch ligado para os wrappers de format de perry/i18n.
  • #189 / #203 — codegen dispatch de perry/plugin.
  • #190 — widget Canvas pelo codegen LLVM.
  • #191 — CameraView pelo codegen.
  • #192 — widget Table pelo codegen.
  • #193 (parcial) — 11 braços de dispatch de helpers da stdlib.
  • #98 — recebimento em background de notificações no iOS + Android (warm-path).
  • #106 — fallbacks fracos para hooks de FFI de game-loop no watchOS.
  • #154 — hooks de dispose de using / await using.
  • #167 — alloca de args de js_native_call_method içada para o bloco de entry.
  • #169 — braços Uint8Array de substitute_locals.
  • #226fs.appendFileSync ligado de ponta a ponta (PR da comunidade).

6. Windows + Scoop

A história da toolchain do Windows continua a se simplificar. v0.5.353 prendeu clang -target em builds de host — clang não-MSVC no PATH (MinGW / MSYS2 / Anaconda / bundles GNU do Rust) reescrevia silenciosamente a IR x86_64-pc-windows-msvc de Perry para windows-gnu, e lld-link não conseguia resolver a referência __main que o emissor mingw32 do LLVM inseria. O novo probe_clang_default_triple roda clang --version uma vez por processo e imprime uma única nota informativa quando o default do host é GNU mas estamos targetando MSVC. Suprimir com PERRY_NO_CLANG_PROBE=1.

v0.5.345 alinhou a ABI perry-ui Win64 com perry-dispatch — três assinaturas extern de runtime tinham deslizado (perry_ui_navstack_create, perry_ui_menu_add_item_with_shortcut, perry_ui_app_set_timer). Na ABI Win64, args posicionais inteiros e float compartilham os índices de slot, então um mismatch lê lixo de registradores não inicializados. SysV (macOS / Linux) usa pools de registradores int/float separados e por sorte caía bits válidos — crash apenas no Windows, corrigido em todas as 8 crates de plataforma perry-ui-*.

Depois: scoop install perry-ts/perry. Manifest fixado em v0.5.345 (com depends: main/llvm para puxar automaticamente o LLVM oficial default-MSVC). O workflow de release agora emite sidecars <artifact>.sha256 ao lado de cada arquivo, em formato compatível com sha256sum para qualquer bumper de package manager downstream.

# Host Windows
scoop bucket add perry-ts https://github.com/PerryTS/perry
scoop install perry-ts/perry
perry compile src\main.ts --target windows -o myapp.exe

7. Encerrando

O padrão deste trecho é engajamento da comunidade mais higiene interna. TheHypnoo entregou três PRs significativos (#224 perry/updater, #231 ligação de fs.appendFileSync, #232 bytes do body em response.arrayBuffer). O tracker esvaziou cerca de 30 issues. O compilador ficou 60% menor no maior arquivo e ganhou um walker exaustivo que transforma «esqueci de atualizar um dos quatro walkers ad-hoc» de um miscompile em runtime para um erro cargo build. O styling UI alcançou paridade em toda plataforma desktop exceto sombras no Windows. Geisterhand ganhou uma superfície devtools em navegador. O caminho de instalação no Windows ficou um comando mais curto.

Experimente:

# npm (qualquer plataforma)
npm install @perryts/perry
npx perry compile src/main.ts -o myapp && ./myapp

# Homebrew (macOS)
brew install PerryTS/perry/perry

# Scoop (Windows)
scoop bucket add perry-ts https://github.com/PerryTS/perry
scoop install perry-ts/perry

# Auto-update para apps desktop
npm install @perry/updater

# Inspector ao vivo
perry compile main.ts -o app --enable-geisterhand
./app &  # então abra http://localhost:7676

Source: github.com/PerryTS/perry — Issues: github.com/PerryTS/perry/issues — Changelog: CHANGELOG.md

— Ralph

Gostou deste post? Receba o próximo.

Notas curtas sobre os releases do Perry e o que vem por aí.

Your email is sent to Resend for the Perry newsletter. You can request unsubscribe or deletion at any time. Privacy notice