Gravar vídeos de demonstração
Escreva um storyboard Playwright ad-hoc e execute o mecanismo autocontido de qualquer diretório de trabalho.
NÃO adicione o storyboard a um repositório de aplicativo, a menos que o usuário solicite.
Mantenha o storyboard autocontido; o executor prepara esse arquivo dentro do mecanismo antes de o Playwright carregá-lo, portanto, não use imports auxiliares relativos ao storyboard.
<skill-dir> significa o diretório onde este repositório foi clonado, por exemplo, ~/.claude/skills/record-demo.
Escreva o storyboard
Importe o DSL com este import portátil:
import { demo } from "record-demo/storyboard";
O executor reescreve este especificador para o caminho real do mecanismo durante a preparação, para que o arquivo de storyboard possa estar em qualquer lugar. A resolução de tipo do editor para record-demo/storyboard vem de engine/tsconfig.json paths - fora do repositório, mapeie record-demo/storyboard para o caminho absoluto <skill-dir>/engine/lib/storyboard.ts em seu paths do tsconfig, ou mantenha o arquivo em engine/scripts/.
Use engine/scripts/example-site.demo.ts como referência. Prefira seletores getByRole, use d.click(locator) em vez de locator.click(), pause as ações com d.hold(...) e afirme ou aguarde o estado final.
demo("título-do-storyboard", async (d) => {
await d.seed(async (page) => {
await page.getByRole("heading", { name: "Example Domain" }).waitFor();
});
await d.step("Leia a página", async (page) => {
const heading = page.getByRole("heading", { name: "Example Domain" });
await d.zoomTo(heading, { padding: 48 });
await d.hold(2_000);
d.zoomOut();
await heading.waitFor({ state: "visible" });
});
});
O mecanismo substitui o nome da linha do tempo por --name ou pelo nome base do script, portanto, a string passada para demo(...) não precisa corresponder ao slug de saída.
Pré-voo (antes de qualquer captura)
Ao gravar contra um checkout servido localmente: git fetch origin no checkout servidor e afirme que HEAD == origin/main (ou o branch nomeado pelo usuário) — um checkout desatualizado grava um design desatualizado. Se estiver desatualizado, reconstrua/reinicie o servidor primeiro. Imprima o SHA do servidor nas notas da captura para que cada gravação seja atribuível.
Gravar
Requer Node >= 22.18 (nvm ou sistema), ffmpeg, Xvfb e xdpyinfo. Instale as dependências uma vez dentro de engine/ com pnpm install.
node <skill-dir>/engine/record.mjs \
--script /caminho/absoluto/ou/relativo-ao-cwd/demo.demo.ts \
--url https://example.org \
[--name slug-de-saída] [--formats 16x9,1x1,9x16] \
[--chrome none|macos|browser] [--storage-state <caminho>] [--raw]
Use --formats para selecionar uma ou mais predefinições de composição (padrão 16x9) e --chrome para escolher o quadro da janela renderizado (padrão none). Os formatos são renderizados na ordem solicitada, e o primeiro é o primário usado para codificar o GIF.
O formato 9x16 é um corte central agressivo (cerca de 44% da largura da página está visível no plano geral), portanto, mantenha a ação centralizada ao criar para retrato.
Use --raw para parar após a captura limpa. Sem ele, espere estes entregáveis:
engine/out/<nome>-raw.mp4engine/out/<nome>-<formato>.mp4para cada formato solicitadoengine/out/<nome>.gifcodificado a partir do formato primário
O executor imprime os caminhos absolutos dos entregáveis quando termina. Trate a reprodutibilidade em sites ativos como responsabilidade do site alvo e nunca incorpore credenciais reais em um storyboard.
Sites autenticados
Grave fluxos autenticados apenas com contas de teste ou de staging; nunca use credenciais de produção. O agente deve primeiro fazer login uma vez com suas próprias ferramentas, usando um script Playwright headless. Se o login exigir um OTP enviado por e-mail ou um link mágico, a rota preferida é o próprio acesso de e-mail do agente - um conector/MCP do Gmail conectado no Claude Code, ou um CLI de e-mail em outros runtimes - para que ele leia o código sozinho; caso contrário, peça ao usuário para colá-lo. Após o login, salve a sessão com await context.storageState({ path: storageStatePath }), onde storageStatePath é um caminho temporário único criado como storageStatePath="$(mktemp -d)/<conta>.storage-state.json". Em seguida, execute o comando de gravação com --storage-state "$storageStatePath".
O storageState do Playwright restaura apenas cookies e localStorage. Aplicativos que mantêm autenticação em IndexedDB ou sessionStorage gravarão silenciosamente uma tela de não logado, portanto, verifique a página autenticada antes de capturar. O arquivo storage-state é um segredo: nunca o imprima ou envie para o repositório. O gravador o exclui imediatamente após carregá-lo; é uma transferência única, portanto, regravar requer um novo login e nada relacionado à autenticação permanece no disco. O script auxiliar de login único também contém credenciais: mantenha-o temporário e nunca o envie para o repositório.
Editar uma demonstração gravada no Remotion Studio
Exporte um projeto autocontido após a gravação, depois instale e abra o Studio:
node <skill-dir>/engine/scripts/export-project.mjs <nome>
cd <skill-dir>/engine/out/<nome>-project
pnpm install
pnpm studio
O painel da linha do tempo do Studio mostra faixas no estilo de editor (Filmstrip da tela, um clipe nomeado por legenda, spotlight, movimento de zoom e encerramento) - arraste o playhead para navegar, clique na linha de um clipe para encontrar seu evento. Use o painel de propriedades ou fixtures/timeline.json para editar legendas, temporização do zoom (mantenha os movimentos de zoom em ou acima do piso de 1650ms), temporização do spotlight e título de encerramento. Edite remotion/theme.ts para cores, tipografia, espaçamento e movimento. Renderize o vídeo 16:9 finalizado com pnpm render.
Regras de movimento e storyboard (o que torna um clipe postável)
- Um movimento de câmera por cena, não por campo. Aplique zoom uma vez na seção, aja dentro dela. Zooms por entrada parecem cortes abruptos.
- Plano geral em transições de página, permaneça em plano geral em páginas esparsas.
d.zoomOut()ANTES de um clique que navega; nunca aplique zoom em um status pill em uma página quase vazia. - Seções próximas ao final da página:
d.zoomTo([...locators], { scroll: "center", padding: 48 })- a pinça da câmera não consegue centralizar um retângulo no canto da página; a rolagem centralizadora corrige isso na raiz.Locator[]enquadra uma seção inteira via bbox de união. d.spotlight([...locators])= foco estilo Apple (escurecimento com recorte + anel).holdMsgeneroso, termine explicitamente comd.spotlightOff()+hold(600)antes do final da etapa. Os retângulos do spotlight devem caber no viewport (aplicado); os retângulos de zoom podem transbordar por design.- Seed espera por dados reais, não apenas pelo elemento de gatilho - caso contrário, o vídeo abre em um esqueleto. Aguarde um nó de conteúdo conhecido + ~600ms.
d.settle(locator)antes das legendas de resultado. As visualizações que buscam novamente após um envio ficam em branco brevemente; umwaitForsimples resolve na primeira pintura e a legenda de resultado dispara sobre uma página branca.- Legendas são imperativos vinculados à ação do cursor; coloque uma legenda de resultado (
d.caption(...)no final da etapa) nas cenas de encerramento; corte as legendas das etapas para que os imperativos nunca fiquem pendentes sobre ações concluídas. d.outro(titulo)fecha clipes postáveis com uma placa final calma após a cena final.- Sempre afirme o estado final - sem ele, o pipeline renderiza um vídeo polido de um fluxo quebrado.
- Nunca envie um clipe com uma URL localhost, hostname interno, nome de provedor ou credenciais reais na tela. Mascare strings de apresentação apenas na seed (patch de window.open e uma reescrita de entrada do MutationObserver).
Verificar antes de relatar como concluído
pnpm typecheck && pnpm testemengine/.node scripts/score-motion.mjs out/timeline.json- FAIL bloqueia; WARN taxa de zoom ~0,78/s é o estado estável aceito; qualquer veredito de descontinuidade é um erro de câmera; WARNs de silêncio morto significam um trecho estático >= 3,5s com menos de 40px de deslocamento do cursor - re-pace ou coloque legenda.node scripts/prove-composition.mjsapós alterações no mecanismo.- Assista à renderização real:
ffmpeg -i out/<nome>-16x9.mp4 -vf fps=1 out/frames/%03d.pnge leia os quadros. Zoom cortado, atraso de legenda, dessincronização do recorte e URLs de desenvolvimento na tela são invisíveis em uma execução de teste verde.
Problemas comuns
| Sintoma | Causa / correção |
|---|---|
Invalid demo viewport ... aborto | A geometria derivou - corrija as flags de inicialização, nunca relaxe a asserção. |
| Deslocamento do cursor ~174px verticalmente | O Chrome foi executado com abas, não --app. |
| Não foi possível encontrar quadros da claquete | A tolerância do detector é 64 (capturas externas médias impuras); se ainda assim falhar, verifique se a captura não está preta (Chrome no DISPLAY errado). |
| Legendas/deslocamento do cursor sobre o clipe | Sincronização da claquete - não subtraia pelo relógio de parede. |
| Recorte do spotlight fora do alvo | O retângulo correu em uma rolagem suave - o loop de estabilidade do measureTarget deve segurar; se recorrer, aumente seu timeout. |
| Palavras cortadas no meio da letra na borda do corte | zoomTo(locator, { padding: 120 }) para modais. |
| Zoom corta conteúdo fora do quadro | Problema de gosto do maximumScale (1,45 funciona), não um bug de pinça. |
Verificar alterações no mecanismo
Execute a partir de engine/:
pnpm typecheck && pnpm test
node scripts/prove-composition.mjs