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/updaterchega — 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:7676com árvore de widgets, detalhe por widget, dispatch de cliques e edição de estilo ao vivo viaPOST /style/:h. - O refactor do compilador. Entre v0.5.329 → v0.5.343 os quatro arquivos mais citados foram fatiados:
lower::lower_expr6.687 → 624 LOC (−91%),compile.rs9.391 → 3.783 LOC (−60%),lower.rs13.591 → 7.554 LOC (−44%),lower_call.rs7.000+ → 4.681 LOC (−33%). O novowalker.rstransforma 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ó faltawidget.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 corretamenteModelo 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_bufferagora aloca umBufferHeaderreal e fazmemcpyderesp.bodydentro.fs.appendFileSyncescrevia 0 bytes. Corrigido em #226 — o caminho de lowering do namespace-import (import * as fs from "fs") não tinha braço paraappendFileSync, 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 prendeINSPECTOR_HTMLcontra o lazy-load-dead_stripdo macOS para sobreviver às release builds. - Passo 2 (v0.5.350) —
POST /style/:hrecebe 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ção | Antes | Depois | Δ |
|---|---|---|---|
find_max_local_id::check_expr | 225 | 57 | −75% |
substitute_locals | 553 | 80 | −86% |
collect_local_refs_expr | 720 | 70 | −90% |
remap_local_ids_in_expr | 542 | 85 | −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:
| Arquivo | Antes | Depois | Δ |
|---|---|---|---|
lower::lower_expr | 6.687 | 624 | −91% |
compile.rs | 9.391 | 3.783 | −60% |
lower.rs | 13.591 | 7.554 | −44% |
lower_call.rs | 7.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_functionse três passes rayon emcompile.rs— economiza 5 scans de módulo + 3 round-trips do scheduler por compilação. - Limitado o parse cache do
perry deva 500 entradas, eviction FIFO. Antes do fix, uma sessão andando pornode_modulespodia segurar mais de 100 MB de AST do SWC. - Paralelizado o loop de escrita
.llpó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 +
parseColorem 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
CALayerparawidget.shadow+ infraestrutura de visual_test; class-probe deset_colorpara widgets que não sãoNSTextField. - iOS / tvOS / visionOS (v0.5.346) — Button com
color: ...batia emsetTextColor:emUIButton, que não implementa esse seletor; o panic doobjc2cruzava uma fronteiraextern "C"e o processo abortava. Corrigido com o mesmo padrão de class-probe do macOS — UIButton agora vai porsetTitleColor:forState:UIControlStateNormal. - Windows (v0.5.347) — 4 de 5 stubs de styling ligados (
text.decorationvia round-tripLOGFONT,widget.opacityviaWS_EX_LAYERED+SetLayeredWindowAttributes, borders viaSetWindowSubclass+WM_PAINT). Só faltawidget.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
fnnã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
constem nível de módulo perdiam as escritasarr[i]=de dentro de funções. Apareceu quandodiscoverLevels()de Bloom-Engine/jump populavaLEVEL_FILESem nível de módulo via index-assign e a tela de seleção de fase saía vazia. - #233 (v0.5.357) —
Array.pushde 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 mecanismoGC_FLAG_FORWARDEDexistente 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, elower_class_methodnão chamavabuild_default_param_stmts. Apareceu emfindOne(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 imprimiaUncaught exception: [object Object](*StringHeadernu NaN-boxado em vez de umErrorHeaderreal). - #234 (v0.5.359) —
Blobreal com métodos de instânciaarrayBuffer/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.areduzido de 196 → 35 objetos sem erros de link. - #220 —
secur32.libadicionado à linha de link do Windows. - #198 — i18n
FormatNumberround-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_methodiçada para o bloco de entry. - #169 — braços Uint8Array de
substitute_locals. - #226 —
fs.appendFileSyncligado 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.exe7. 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:7676Source: 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