O que demo-video faz?
bun run demo <funcionalidade>/<clipe> grava uma tomada em um simulador iOS alugado e renderiza um vídeo 1920x1080: título à esquerda, um terminal que digita cada comando warden real e imprime sua saída quando a etapa termina, o telefone à direita (arredondado, com borda de 3px). Kit: scripts/demo/ (demo.ts CLI, record.ts, render.py, timeline.ts, demo.types.ts). Definições: apps/example/demos/<funcionalidade>/<clipe>.demo.ts (name deve ser igual a <funcionalidade>/<clipe>); ativos: apps/docs/public/demos/<funcionalidade>/<clipe>.mp4|jpg. Uma pasta por funcionalidade (biometrics, network, location, clock), minúsculas, hífens. Interno: sem changeset.
1. Quando criar um
- Uma funcionalidade cujo efeito seja visível na tela e aconteça em segundos (biometria, alertas, gestos). Não usar para logs, números de desempenho ou qualquer coisa que uma tabela mostre melhor.
- Após a interface do aplicativo ou o comportamento de um comando ter mudado: regravar, nunca editar o mp4.
- Uma história por clipe, 10-25 s, 3-6 etapas legendadas.
Requer ffmpeg, ffprobe, uv (brew install ffmpeg uv), Xcode e as fontes em node_modules/@expo-google-fonts (bun install).
2. Escrever uma definição
Exportar demo: DemoDefinition (scripts/demo/demo.types.ts). Copiar apps/example/demos/biometrics/faceid.demo.ts.
| Campo | Significado |
|---|---|
title / subtitle / terminalTitle / footer | texto estático; título é a afirmação principal, subtítulo uma linha |
phoneCaption | linha abaixo do telefone, padrão real simulator · not an animation; "" a oculta |
claim | { biometrics?, profile? } -> warden claim ios --json --label demo-<funcionalidade>-<clipe> --ttl 30m [--biometrics] |
app | bundleId, kind (simulator padrão), project (cwd relativo ao repositório), deepLink aberto com --settle, appearance (light/dark, definido via simctl ui no udid alugado, restaurado depois) |
setup | warden argv executado fora da câmera após a abertura do aplicativo, antes do deep link; ["ui","network","set","online","--relaunch"] dá o lançamento do dylib iOS que as etapas de rede/relógio precisam. Uma entrada é um argv ou { argv, optional: true }: uma etapa opcional que sai com código não zero é registrada (optional setup warden … -> <código>) em vez de falhar a tomada (um prompt de permissão que pode não aparecer) |
ready | seletores warden ui wait que devem passar após o deep link e antes do vídeo começar (ex.: id=bio-screen); uma etapa nunca pressiona uma tela que não montou |
leadInMs / tailHoldMs | tempo ocioso antes da etapa 1 / último quadro mantido, preenchido pelo renderizador (a cauda da tomada tem 1 s) |
steps[] | argv (após warden; o gravador anexa --udid), output (ok desenha uma marca de verificação vetorial, plain é mudo, lines imprime várias linhas mudas), pauseAfterMs, shown?, hidden?, quote? |
outro | linha # comment digitada após a última saída |
output: { kind: "lines", text: [...] } imprime uma linha muda por entrada (mantenha cerca de 6; cada uma após a primeira custa 0,4 de um pitch de entrada no layout) para código ou texto mostrado de um arquivo. Combine com quote: { file } (caminho relativo a app.project): após a etapa, o gravador verifica se cada linha impressa, aparada, ocorre nesse arquivo e lança quoted line not in <arquivo>: <linha> caso contrário, garantindo que uma linha citada nunca seja inventada. As linhas ainda passam por maskSecrets.
Os tempos nunca são escritos à mão: o gravador mede o início e o fim de cada etapa em relação ao relógio do vídeo e a linha de saída aparece no FIM da etapa.
3. O loop
bun run demo <funcionalidade>/<clipe> --record-only # claim, barra de status 9:41, lançar, gravar, liberar por id
bun run demo <funcionalidade>/<clipe> --render-only --sheet # construir mp4 + jpg, escrever <work>/sheet.jpg
bun run demo <funcionalidade>/<clipe> # ambos
Diretório de trabalho: $TMPDIR/warden-demo/<funcionalidade>/<clipe> (--out dir): take.mp4, steps.json, plan.json, sheet.jpg; em uma tomada com falha failure-snapshot.txt (erro + ui snapshot -i). Nada disso é commitado.
- Gravar.
- Ler
sheet.jpg(blocos 4x4, 1 por segundo, pts queimados). Verificar contra o log: cada linha de saída aparece quando o telefone reage visivelmente; nenhum quadro mostra um alerta não relacionado, teclado, red box ou barra de status errada; o último quadro mostra o estado final. - Para uma inspeção mais detalhada:
ffmpeg -ss 7 -i take.mp4 -frames:v 1 f.pngeRead. - Corrigir na definição (
pauseAfterMs, descartar/ocultar uma etapa,shown) e--render-only; regravar apenas quando a própria tomada estava errada. Correções em lote: uma gravação por rodada. - Commitar o mp4 + jpg em
apps/docs/public/demos/<funcionalidade>/.
4. Regras de honestidade
- Todo comando impresso foi executado, contra o aplicativo real. Nenhum
shownque difira em significado deargv(ele apenas corta flags como--timeout). - O texto de saída é o que o telefone fez, não o que o comando imprimiu. Se um toque não tem efeito visível (descartado durante a animação de entrada de uma folha, nada mudou), não legende:
hidden: true, ou corrija o timing com umpauseAfterMsanterior mais longo. - Alertas de permissão de primeira execução aparecem apenas em uma instalação nova; o claim reutiliza um build aquecido, portanto não legende um alerta a menos que a tomada o mostre.
- Nenhum bypass no lado do aplicativo ou prompt simulado: a afirmação no título deve ser verdadeira na tomada.
- Segredos:
maskSecretsoculta key=value, tokens estiloghp_/sk-e hex/base64 longos nas linhas impressas. Ainda verifique o telefone em busca de tokens, e-mails, nomes de dispositivos. - Simulador real, interface iOS real. Nunca componha quadros falsos.
5. Pegadinhas (codificadas no kit)
- O vídeo simctl é VFR e descarta a cauda ociosa: o renderizador normaliza o
fpse preenche o último quadro (tpad) até a duração do plano. - O tempo do vídeo corresponde ao log de comandos dentro de ~0,5 s; o gravador inicia o relógio logo após
video start. - Martian Mono não tem glifo de verificação:
render.pyo desenha como vetores. - O quadro é a paleta escura do Device Hub da documentação (Mona Sans + Martian Mono); defina
app.appearance: "dark"para que o telefone corresponda. O gravador restaura a aparência do simulador depois. - Estouro do terminal:
computeTerminalLayoutreduz o pitch e a fonte para 17 px e rola as entradas mais antigas para fora. - Limite de vídeo do dispositivo 3 min; mantenha as tomadas abaixo de 40 s de qualquer forma.
- Barra de status:
xcrun simctl status_bar <udid> overrideé permitido em um udid alugado; o gravador a limpa depois.
6. Exportação para X
1920x1080, H.264 High, yuv420p, 30 fps, +faststart, sem áudio, <= 2:20, arquivo pequeno (kit visa <= 2 MB através de uma escada CRF 24..36). Verificar:
ffprobe -v error -show_entries stream=codec_name,profile,width,height,r_frame_rate -show_entries format=duration,size apps/docs/public/demos/<funcionalidade>/<clipe>.mp4
7. Ativos e incorporação na documentação
Saída: apps/docs/public/demos/<funcionalidade>/<clipe>.mp4 + <clipe>.jpg (pôster = último quadro), servidos em /demos/<funcionalidade>/<clipe>.mp4 (Vite public/). Embutir com o componente MDX DemoClip (apps/docs/src/components/demo-clip.tsx): reproduz automaticamente sem som e em loop, para no pôster em movimento reduzido. src não tem extensão; label diz o que o clipe mostra (corresponder à legenda). Um <video> bruto falha em apps/docs/src/lib/source.test.ts.
<DemoClip src="/demos/<funcionalidade>/<clipe>" label="warden ui … fazendo … em um simulador alugado" />
Adicione uma frase de legenda nomeando os comandos. O carretel em lote apps/docs/public/demos/e2e/batch.* (embutido com <DemoReel>) é um pipeline diferente (bun run demo:compose).
8. O carretel warden dev (dev/hero)
Um terceiro pipeline: o painel de desenvolvimento REAL do warden (esquerda, em um cartão de terminal) ao lado do simulador que warden dev reivindica a si mesmo (direita). Nada é digitado ou falsificado: dev-take.ts inicia warden dev ios --label demo-dev-<clipe> --headless --no-shutdown em um PTY (Bun.spawn({ terminal })) espelhado em @xterm/headless (leituras de quadro para as esperas) e escreve cada bloco do PTY em tui.cast; a origem do relógio é o início. Ele consulta warden ls --json pela concessão de dispositivo rotulada, então define escuro + 9:41, desinstala o aplicativo e inicia ui screen video (o telefone fica preto até então). O filho dev recebe seu próprio WARDEN_SESSION_ID: warden dev adota uma concessão de dispositivo mantida pela sessão do chamador, e cada agente de equipe compartilha uma, então sem isso a tomada poderia dirigir o simulador de outra pessoa.
bun run demo:dev dev/hero --record-only # claim, desinstalar app, 9:41 + escuro, gravar, liberar por id
bun run demo:dev dev/hero --render-only --sheet # agg + ffmpeg -> apps/docs/public/demos/dev/hero.{mp4,webm,jpg}, <work>/sheet.jpg
bun run demo:dev dev/hero # ambos
- Kit:
dev-demo.ts(CLI),dev-take.ts(gravador,runBeats),dev-render.ts(agg + compor,DEV_LAYOUT),dev-frame.py(cartão, anel, máscara),dev-timeline.ts(texto do cast, segmentos de rampa, legendas). Tipos:DevDemoDefinition/DevBeatemdemo.types.ts. Definição:apps/example/demos/dev/<clipe>.demo.ts; diretório de trabalho$TMPDIR/warden-demo/dev/<clipe>(take.mp4,tui.cast,dev.json,sheet.jpg,failure-frame.txt). - Um beat =
waitScreen(regex sobre o quadro TUI) +waitPhone(seletoresui wait) -> ação (keysna TUI, e/ou umeditreal de um arquivo do repositório) -> seletoresafterPhone(o pouso do hot reload) ->pauseAfterMs.dev.jsoncontém o início / pronto / fim de cada beat no relógio do vídeo; as legendas vão do início da ação de um beat até o próximo. - A edição é uma edição real do código fonte (string do corpo de
welcome.tsx), revertida byte a byte emfinally; verifique segit diff apps/example/srcestá vazio após uma tomada. uninstallFirstremove o aplicativo do sim alugado para quewarden devinstale o build de desenvolvimento do cache na câmera. A PRIMEIRA tomada em uma impressão digital é um build local (minutos, a linhadev buildcompila): grave uma vez para aquecer o cache, depois regrave.warden app ensure --kind devnão aquece o cache quewarden devlê.- Rampas de velocidade:
DevBeat.ramp: Nacelera a espera desse beat por N (mantendo 1,2 s de cabeça e 1 s de cauda em velocidade real) e o carretel desenha uma etiqueta× N speed. Uma tomada aquecida que é lenta na espera do cliente dev a acelera. As teclas e o hot reload são sempre em tempo real. - Honestidade: os fatos na legenda
<DemoReel>vêm das linhas TUI da tomada (dev build 3.5s,Metro 3.9s,dev client 161ms) edev.json(edit ->ui waitvê o novo texto: 0,76 s). Releia-os após cada regravação; eles mudam. - Limites: o painel não tem linha de claim separada (está dentro de
preparing the ios dev build);--headlesspara que nenhuma janela do Device Hub seja aberta; PTY 100x26 (100 colunas é o mínimo do HUD). Um beat comstopVideoFirstpara o vídeo antes de sua ação (qencerra a concessão ewarden uientão se recusa). Apósqa tomada verifica sewarden lsnão possui nenhuma concessão da sessão (falha caso contrário), e o cast é cortado onde o painel deixa a tela alternativa para que o carretel termine em seu último quadro. A tela de carregamento branca do launcher dev e o banner azulRefreshing…são o aplicativo real; um recarregamento do Metro também pode vir de outro agente editandoapps/example/src. - Rampas:
rampsó se aplica quando a espera, menos sua cabeça de 1,2 s e cauda de 1 s, tem pelo menos 6 s (RAMP_MIN_S). O herói acelera sua espera do cliente dev em 3x com uma etiqueta. devEnvadiciona env ao filhowarden dev; o herói defineEXPO_NO_DEPENDENCY_VALIDATION=1(@expo/clicheckDependenciesOnStart) para remover o aviso "update available" do Expo do painel Metro. Use-o apenas para ruído, nunca para comportamento.- Embutir com
<DemoReel src="/demos/dev/hero" command="warden dev ios" facts="…" label="…" />(herói da página inicial,sims/dev.mdx,getting-started/quickstart.mdx). Pôster = o quadro da açãoposterBeat+ 1 s (HUD ligado, todas as linhas concluídas, aplicativo em execução).
9. Clipes com muito terminal (VID-6)
Todos comandos reais; cada carretel mostra apenas o que foi executado. Um clipe de cada vez, liberar por etiqueta após cada.
| Clipe | Pipeline | Comando |
|---|---|---|
e2e/parallel | batch-take.ts: warden e2e <suíte> --count N --record --headless em <work>/rec, estilizando cada sim alugado escuro + 9:41 enquanto inicializa, depois compose.ts --height 1080 | bun run demo:batch e2e/parallel --suite example --specs devtools,gestures,onboarding,sign-in,todo-detail --count 2 (--record-only / --compose-only; cabeçalho mostra contagem de specs; contador lê N/total shards done quando specs diferem de jobs) |
dev/worktrees | LanesDemoDefinition: um recordDev por faixa concorrentemente, uma faixa de shell (warden ls) abaixo | bun run demo:lanes dev/worktrees (lanes.ts, lanes-frame.py, lanes-demo.ts; faixas compartilham um relógio via DevTake.startedAtS) |
golden/clone | DevDemoDefinition.script: um script shell é executado no PTY em vez de warden dev | bun run demo:dev golden/clone |
debug/failure | script + rawVideo | bun run demo:dev debug/failure --sheet |
- Modo
script: sem desinstalação, sem verificação de liberação; a tomada termina comrelease --mine --label L --shutdownesims delete <udid> --yes. Um beat comwaitBootconsultasimctl io screenshotaté que o PNG exceda 100 KB (HOME_SCREEN_MIN_BYTES): um simulador headless recém-clonado mostra o logotipo da Apple por 10+ s apósclaimretornar. Acelere essa espera (3x, com etiqueta). rawVideo: grava comsimctl io recordVideo(SIGINT para parar) porque o auxiliarui screen videodo warden entra em conflito comwarden e2eem um dispositivo.--recordsó grava dispositivos presentes no início; participantes tardios elásticos não têm vídeo. O gate de capacidade do host define quantos cabem, então não peça mais sims do que os que foram inicializados no início (máx. 2 aqui).- A spec descartável para
debug/failureé escrita emapps/example/e2e/debug-demo.e2e.tse removida por um trap do shell; verifiquegit statusdepois (uma tomada morta pode deixá-la). - Embutir com
<DemoReel src="/demos/<funcionalidade>/<clipe>" command="…" facts="…" label="…" />; os fatos vêm da tomada, releia após cada regravação. demo:batchestiliza sims com um poller (styleWhenBooted:warden ls --jsonpela etiqueta,simctl listpara Booted, depoisui appearance dark+status_bar override 9:41) porquewarden e2enão tem hook por dispositivo; ele libera--mine --label demo-<funcionalidade>-<clipe> --shutdownemfinally. Use specs que passam em um sim limpo (perf,settings,todosnão são confiáveis); a flag de composição--height 1080fornece o layout 16:9 (terminal...