Communitygithub.com

ui-demo

Grave vídeos de demonstração polidos de aplicações web usando Playwright, com cursor visível, ritmo natural e sensação profissional.

O que é ui-demo?

O UI Demo Video Recorder é uma skill para agentes de IA que permite gravar vídeos de demonstração de alta qualidade de aplicações web usando Playwright. A skill adota um processo de três fases: Descoberta, Ensaio e Gravação, garantindo que cada vídeo seja polido, profissional e confiável. Durante a fase de Descoberta, o agente explora as páginas alvo para mapear campos de formulário, seletores e conteúdo dinâmico, evitando suposições que poderiam quebrar a gravação. A fase de Ensaio verifica cada seletor antes da gravação, usando uma função `ensureVisible` que falha ruidosamente se um elemento não for encontrado, evitando erros silenciosos. A fase de Gravação produz vídeos WebM com resolução 1280x720, injeta um cursor SVG personalizado que segue os movimentos do mouse, usa digitação visível (em vez de preenchimento instantâneo), movimentos suaves do mouse e uma barra de legendas opcional para narrar os passos. A skill é ideal para criar vídeos para documentação, onboarding, apresentações para stakeholders ou tutoriais, e funciona com Playwright em ambientes Node.js. O foco está em contar uma história visual: entrada, contexto, ação, variação e resultado, com pausas naturais entre as ações para dar ao espectador tempo para processar.

Funciona com~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/ui-demo

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Gravador de Vídeo de Demonstração de UI

Grave vídeos de demonstração polidos de aplicações web usando a gravação de vídeo do Playwright com um overlay de cursor injetado, ritmo natural e fluxo narrativo.

Quando Usar

  • O usuário pede um "vídeo de demonstração", "gravação de tela", "passo a passo" ou "tutorial"
  • O usuário quer mostrar um recurso ou fluxo de trabalho visualmente
  • O usuário precisa de um vídeo para documentação, integração ou apresentação para stakeholders

Processo de Três Fases

Toda demonstração passa por três fases: Descobrir -> Ensaio -> Gravar. Nunca pule direto para a gravação.


Fase 1: Descobrir

Antes de escrever qualquer script, explore as páginas alvo para entender o que realmente está lá.

Por quê

Você não pode escrever um script para o que não viu. Campos podem ser <input> e não <textarea>, menus suspensos podem ser componentes personalizados e não <select>, e caixas de comentários podem suportar @menções ou #tags. Suposições quebram as gravações silenciosamente.

Como

Navegue para cada página no fluxo e despeje seus elementos interativos:

// Execute isso para cada página no fluxo ANTES de escrever o script de demonstração
const fields = await page.evaluate(() => {
  const els = [];
  document.querySelectorAll('input, select, textarea, button, [contenteditable]').forEach(el => {
    if (el.offsetParent !== null) {
      els.push({
        tag: el.tagName,
        type: el.type || '',
        name: el.name || '',
        placeholder: el.placeholder || '',
        text: el.textContent?.trim().substring(0, 40) || '',
        contentEditable: el.contentEditable === 'true',
        role: el.getAttribute('role') || '',
      });
    }
  });
  return els;
});
console.log(JSON.stringify(fields, null, 2));

O que procurar

  • Campos de formulário: São <select>, <input>, menus suspensos personalizados ou comboboxes?
  • Opções de seleção: Despeje os valores E os textos das opções. Placeholders geralmente têm value="0" ou value="" que parecem não vazios. Use Array.from(el.options).map(o => ({ value: o.value, text: o.text })). Pule opções onde o texto inclui "Selecionar" ou o valor é "0".
  • Rich text: A caixa de comentários suporta @menções, #tags, markdown ou emoji? Verifique o texto do placeholder.
  • Campos obrigatórios: Quais campos bloqueiam o envio do formulário? Verifique required, * nos rótulos e tente enviar vazio para ver erros de validação.
  • Conteúdo dinâmico: Os campos aparecem depois que outros campos são preenchidos?
  • Rótulos dos botões: Texto exato como "Enviar", "Enviar Solicitação" ou "Enviar".
  • Cabeçalhos das colunas da tabela: Para modais orientados por tabela, mapeie cada input[type="number"] para seu cabeçalho de coluna em vez de assumir que todas as entradas numéricas significam a mesma coisa.

Saída

Um mapa de campo para cada página, usado para escrever seletores corretos no script. Exemplo:

/purchase-requests/new:
  - Código Orçamentário: <select> (primeiro select na página, 4 opções)
  - Entrega Desejada: <input type="date">
  - Contexto: <textarea> (não input)
  - Tabela BOM: células editáveis inline com span.cursor-pointer -> padrão input
  - Enviar: <button> text="Enviar"

/purchase-requests/N (detalhe):
  - Comentário: <input placeholder="Digite uma mensagem..."> suporta tags @usuário e #PR
  - Enviar: <button> text="Enviar" (desabilitado até que a entrada tenha conteúdo)

Fase 2: Ensaio

Execute todas as etapas sem gravação. Verifique se cada seletor funciona.

Por quê

Falhas silenciosas de seletores são o principal motivo pelo qual as gravações de demonstração quebram. O ensaio as detecta antes de você desperdiçar uma gravação.

Como

Use ensureVisible, um invólucro que registra e falha ruidosamente:

async function ensureVisible(page, locator, label) {
  const el = typeof locator === 'string' ? page.locator(locator).first() : locator;
  const visible = await el.isVisible().catch(() => false);
  if (!visible) {
    const msg = `FALHA NO ENSAIO: "${label}" não encontrado - seletor: ${typeof locator === 'string' ? locator : '(objeto locator)'}`;
    console.error(msg);
    const found = await page.evaluate(() => {
      return Array.from(document.querySelectorAll('button, input, select, textarea, a'))
        .filter(el => el.offsetParent !== null)
        .map(el => `${el.tagName}[${el.type || ''}] "${el.textContent?.trim().substring(0, 30)}"`)
        .join('\n  ');
    });
    console.error('  Elementos visíveis:\n  ' + found);
    return false;
  }
  console.log(`ENSAIO OK: "${label}"`);
  return true;
}

Estrutura do script de ensaio

const steps = [
  { label: 'Campo de email de login', selector: '#email' },
  { label: 'Botão de envio de login', selector: 'button[type="submit"]' },
  { label: 'Botão Nova Solicitação', selector: 'button:has-text("Nova Solicitação")' },
  { label: 'Selecionar Código Orçamentário', selector: 'select' },
  { label: 'Data de entrega', selector: 'input[type="date"]:visible' },
  { label: 'Campo de descrição', selector: 'textarea:visible' },
  { label: 'Botão Adicionar Item', selector: 'button:has-text("Adicionar Item")' },
  { label: 'Botão Enviar', selector: 'button:has-text("Enviar")' },
];

let allOk = true;
for (const step of steps) {
  if (!await ensureVisible(page, step.selector, step.label)) {
    allOk = false;
  }
}
if (!allOk) {
  console.error('ENSAIO FALHOU - corrija os seletores antes de gravar');
  process.exit(1);
}
console.log('ENSAIO APROVADO - todos os seletores verificados');

Quando o ensaio falha

  1. Leia o despejo de elementos visíveis.
  2. Encontre o seletor correto.
  3. Atualize o script.
  4. Execute o ensaio novamente.
  5. Só prossiga quando todos os seletores passarem.

Fase 3: Gravar

Somente após a descoberta e o ensaio passarem, crie a gravação.

Princípios de Gravação

1. Fluxo Narrativo

Planeje o vídeo como uma história. Siga a ordem especificada pelo usuário ou use este padrão:

  • Entrada: Login ou navegue para o ponto de partida
  • Contexto: Passeie pelos arredores para que os espectadores se orientem
  • Ação: Execute as principais etapas do fluxo de trabalho
  • Variação: Mostre um recurso secundário, como configurações, tema ou localização
  • Resultado: Mostre o resultado, confirmação ou novo estado

2. Ritmo

  • Após o login: 4s
  • Após a navegação: 3s
  • Após clicar em um botão: 2s
  • Entre etapas principais: 1.5-2s
  • Após a ação final: 3s
  • Atraso de digitação: 25-40ms por caractere

3. Overlay de Cursor

Injete um cursor de seta SVG que segue os movimentos do mouse:

async function injectCursor(page) {
  await page.evaluate(() => {
    if (document.getElementById('demo-cursor')) return;
    const cursor = document.createElement('div');
    cursor.id = 'demo-cursor';
    cursor.innerHTML = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
      <path d="M5 3L19 12L12 13L9 20L5 3Z" fill="white" stroke="black" stroke-width="1.5" stroke-linejoin="round"/>
    </svg>`;
    cursor.style.cssText = `
      position: fixed; z-index: 999999; pointer-events: none;
      width: 24px; height: 24px;
      transition: left 0.1s, top 0.1s;
      filter: drop-shadow(1px 1px 2px rgba(0,0,0,0.3));
    `;
    cursor.style.left = '0px';
    cursor.style.top = '0px';
    document.body.appendChild(cursor);
    document.addEventListener('mousemove', (e) => {
      cursor.style.left = e.clientX + 'px';
      cursor.style.top = e.clientY + 'px';
    });
  });
}

Chame injectCursor(page) após cada navegação de página porque o overlay é destruído na navegação.

4. Movimento do Mouse

Nunca teletransporte o cursor. Mova para o alvo antes de clicar:

async function moveAndClick(page, locator, label, opts = {}) {
  const { postClickDelay = 800, ...clickOpts } = opts;
  const el = typeof locator === 'string' ? page.locator(locator).first() : locator;
  const visible = await el.isVisible().catch(() => false);
  if (!visible) {
    console.error(`AVISO: moveAndClick ignorado - "${label}" não visível`);
    return false;
  }
  try {
    await el.scrollIntoViewIfNeeded();
    await page.waitForTimeout(300);
    const box = await el.boundingBox();
    if (box) {
      await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2, { steps: 10 });
      await page.waitForTimeout(400);
    }
    await el.click(clickOpts);
  } catch (e) {
    console.error(`AVISO: moveAndClick falhou em "${label}": ${e.message}`);
    return false;
  }
  await page.waitForTimeout(postClickDelay);
  return true;
}

Cada chamada deve incluir um label descritivo para depuração.

5. Digitação

Digite visivelmente, não preencha instantaneamente:

async function typeSlowly(page, locator, text, label, charDelay = 35) {
  const el = typeof locator === 'string' ? page.locator(locator).first() : locator;
  const visible = await el.isVisible().catch(() => false);
  if (!visible) {
    console.error(`AVISO: typeSlowly ignorado - "${label}" não visível`);
    return false;
  }
  await moveAndClick(page, el, label);
  await el.fill('');
  await el.pressSequentially(text, { delay: charDelay });
  await page.waitForTimeout(500);
  return true;
}

6. Rolagem

Use rolagem suave em vez de saltos:

await page.evaluate(() => window.scrollTo({ top: 400, behavior: 'smooth' }));
await page.waitForTimeout(1500);

7. Passeio pelo Painel

Ao mostrar um painel ou página de visão geral, mova o cursor pelos elementos principais:

async function panElements(page, selector, maxCount = 6) {
  const elements = await page.locator(selector).all();
  for (let i = 0; i < Math.min(elements.length, maxCount); i++) {
    try {
      const box = await elements[i].boundingBox();
      if (box && box.y < 700) {
        await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2, { steps: 8 });
        await page.waitForTimeout(600);
      }
    } catch (e) {
      console.warn(`AVISO: panElements ignorou elemento ${i} (seletor: "${selector}"): ${e.message}`);
    }
  }
}

8. Legendas

Injete uma barra de legendas na parte inferior da janela de visualização:

async function injectSubtitleBar(page) {
  await page.evaluate(() => {
    if (document.getElementById('demo-subtitle')) return;
    const bar = document.createElement('div');
    bar.id = 'demo-subtitle';
    bar.style.cssText = `
      position: fixed; bottom: 0; left: 0; right: 0; z-index: 999998;
      text-align: center; padding: 12px 24px;
      background: rgba(0, 0, 0, 0.75);
      color: white; font-family: -apple-system, "Segoe UI", sans-serif;
      font-size: 16px; font-weight: 500; letter-spacing: 0.3px;
      transition: opacity 0.3s;
      pointer-events: none;
    `;
    bar.textContent = '';
    bar.style.opacity = '0';
    document.body.appendChild(bar);
  });
}

async function showSubtitle(page, text) {
  await page.evaluate((t) => {
    const bar = document.getElementById('demo-subtitle');
    if (!bar) return;
    if (t) {
      bar.textContent = t;
      bar.style.opacity = '1';
    } else {
      bar.style.opacity = '0';
    }
  }, text);
  if (text) await page.waitForTimeout(800);
}

Chame injectSubtitleBar(page) junto com injectCursor(page) após cada navegação.

Padrão de uso:

await showSubtitle(page, 'Etapa 1 - Fazendo login');
await showSubtitle(page, 'Etapa 2 - Visão geral do painel');
await showSubtitle(page, '');

Diretrizes:

  • Mantenha o texto da legenda curto, idealmente com menos de 60 caracteres.
  • Use o formato Etapa N - Ação para consistência.
  • Limpe a legenda durante pausas longas onde a IU pode falar por si.

Modelo de Script

'use strict';
const { chromium } = require('playwright');
const path = require('path');
const fs = require('fs');

const BASE_URL = process.env.QA_BASE_URL || 'http://localhost:3000';
const VIDEO_DIR = path.join(__dirname, 'screenshots');
const OUTPUT_NAME = 'demo-FEATURE.webm';
const REHEARSAL = process.argv.includes('--rehearse');

// Cole injectCursor, injectSubtitleBar, showSubtitle, moveAndClick,
// typeSlowly, ensureVisible e panElements aqui.

(async () => {
  const browser = await chromium.launch({ headless: true });

  if (REHEARSAL) {
    const context = await browser.newContext({ viewport: { width: 1280, height: 720 } });
    const page = await context.newPage();
    // Navegue pelo fluxo e execute ensureVisible para cada seletor.
    await browser.close();
    return;
  }

  const context = await browser.newContext({
    recordVideo: { dir: VIDEO_DIR, size: { width: 1280, height: 720 } },
    viewport: { width: 1280, height: 720 }
  });
  const page = await context.newPage();

  try {
    await injectCursor(page);
    await injectSubtitleBar(page);

    await showSubtitle(page, 'Etapa 1 - Fazendo login');
    // ações de login

    await page.goto(`${BASE_URL}/dashboard`);
    await injectCursor(page);
    await injectSubtitleBar(page);
    await showSubtitle(page, 'Etapa 2 - Visão geral do painel');
    // passeio pelo painel

    await showSubtitle(page, 'Etapa 3 - Fluxo de trabalho principal');
    // sequência de ações

    await showSubtitle(page, 'Etapa 4 - Resultado');
    // revelação final
    await showSubtitle(page, '');
  } catch (err) {
    console.error('ERRO NA DEMONSTRAÇÃO:', err.message);
  } finally {
    await context.close();
    const video = page.video();
    if (video) {
      const src = await video.path();
      const dest = path.join(VIDEO_DIR, OUTPUT_NAME);
      try {
        fs.copyFileSync(src, dest);
        console.log('Vídeo salvo:', dest);
      } catch (e) {
        console.error('ERRO: Falha ao copiar vídeo:', e.message);
        console.error('  Origem:', src);
        console.error('  Destino:', dest);
      }
    }
    await browser.close();
  }
})();

Uso:

# Fase 2: Ensaio
node demo-script.cjs --rehearse

# Fase 3: Gravar
node demo-script.cjs

Lista de Verificação Antes de Gravar

  • Fase de descoberta concluída
  • Ensaio passa com todos os seletores OK
  • Modo headless ativado
  • Resolução definida para 1280x720
  • Overlays de cursor e legenda reinjetados após cada navegação
  • showSubtitle(page, 'Etapa N - ...') usado nas principais transições
  • moveAndClick usado para todos os cliques com rótulos descritivos
  • typeSlowly usado para entrada visível
  • Sem capturas silenciosas; mensagens de erro são registradas e a gravação continua, mas são revisadas posteriormente
  • Atrasos definidos: 4s após login, 3s após navegação, 2s após clique, 1.5-2s entre etapas, 3s no final
  • Vídeo de saída é .webm

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

Habilidades Relacionadas