chore(plan-issue): rascunho UI/UX do gate vira HTML+Tailwind commitado no repo (ad-hoc)
This commit is contained in:
parent
b604157fc7
commit
631635ee89
9 changed files with 164 additions and 75 deletions
|
|
@ -4,10 +4,10 @@ description: >-
|
|||
Transforma ideias humanas em Issues GitHub + planos de intenção em
|
||||
docs/plans/ (persona, fluxo, objetivo, direção suave no código — sem
|
||||
decisões duras de engenharia). Se o item muda UI, apresenta no gate um
|
||||
Cursor canvas com rascunho visual UI/UX. Divide o pedido nas menores
|
||||
tarefas que ainda fazem sentido. Use quando o usuário pedir /plan-issue,
|
||||
planejar features, fatiar um pedido em Issues, ou registrar trabalho novo
|
||||
na fila.
|
||||
rascunho visual HTML+Tailwind (PNG embutido no plano de intenção). Divide
|
||||
o pedido nas menores tarefas que ainda fazem sentido. Use quando o usuário
|
||||
pedir /plan-issue, planejar features, fatiar um pedido em Issues, ou
|
||||
registrar trabalho novo na fila.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ rascunho local (Issue: —)
|
|||
4. **Promote só depois do plano em `main`.** Nunca flipar para `ready` com o PR ainda aberto — isso recria a race de claim. Caminhos: (A) `pnpm agent:ready -- --issue N` no fim do Passo 6 após merge; (B) Action determinística no merge que lê `Related #N` (OPS18). Ambos idempotentes.
|
||||
5. **Planos de Issues `in-progress` / `done` / `in-prod` são imutáveis.** Não editar `docs/plans/<slug>.md` nem o body de intenção dessas Issues. Refino → **plano + Issue novos** (sucessor; `depends` no pai se fizer sentido). Enquanto a Issue ainda é só `blocked`/`ready` (sem claim), editar o mesmo plano ainda é barato.
|
||||
|
||||
**Canvas UI (obrigatório se muda UI):** itens com superfície Impeccable **B / C / D** (ou qualquer mudança do que o usuário vê/toca) devem ter um **Cursor canvas** de rascunho UI/UX no gate — ver [ui-draft-canvas.md](ui-draft-canvas.md). Classe A / sem UI → sem canvas.
|
||||
**Rascunho UI (obrigatório se muda UI):** itens com superfície Impeccable **B / C / D** (ou qualquer mudança do que o usuário vê/toca) devem ter um **rascunho HTML+Tailwind** (renderizado em PNG e commitado em `docs/plans/`) no gate — ver [ui-draft-html.md](ui-draft-html.md). Classe A / sem UI → sem rascunho.
|
||||
|
||||
## Divisão com as skills de execução
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ rascunho local (Issue: —)
|
|||
| **`work-issue`** | Humano supervisiona: Issue já claimada (claim fora da skill) → plano de **implementação** (Plan mode) → **pausa** para confirmação → executa. |
|
||||
| **`agent-work-issue`** | Pool / autonomia: Issue já claimada → plano de implementação → executa sem pausa → `/simplify` → `capture-review-debts` → PR Ready + auto-merge. |
|
||||
|
||||
Aqui **não** se implementa código de produto, **não** se escreve plano de implementação, **não** se roda Impeccable craft/critique/polish. O canvas de rascunho UI/UX é artefato do **gate de intenção**, não entrega de app.
|
||||
Aqui **não** se implementa código de produto, **não** se escreve plano de implementação, **não** se roda Impeccable craft/critique/polish. O rascunho UI/UX (HTML+Tailwind) é artefato do **gate de intenção**, não entrega de app.
|
||||
|
||||
**Shaping (não tour):** aplique [shaping.md](shaping.md) em silêncio — appetite, fatia mínima útil, rabbit holes de produto, self-score ≥4 antes de gravar.
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ Aqui **não** se implementa código de produto, **não** se escreve plano de imp
|
|||
- [ ] 2. Reserva de IDs de uma vez por trilha (roadmap legacy + issuesById())
|
||||
- [ ] 3. Por item (ordem topológica): classificar → fatiar → explorar só o suficiente → intenção completa
|
||||
- [ ] 4. Sugestão de modelo × effort via model-selection (uma linha por item)
|
||||
- [ ] 5. GATE: overview do lote + canvas UI/UX (se muda UI) + esboços de fluxo → confirmar/iterar (PARAR aqui até o humano confirmar; sem Issue/PR)
|
||||
- [ ] 5. GATE: overview do lote + rascunho UI/UX (se muda UI) + esboços de fluxo → confirmar/iterar (PARAR aqui até o humano confirmar; sem Issue/PR)
|
||||
- [ ] 6. Registro: `agent:register` (`--plan` → `blocked`) → PR `Related #N` → merge → `pnpm agent:ready`
|
||||
```
|
||||
|
||||
|
|
@ -92,10 +92,10 @@ Ordem topológica (dependente cita ID do dependido). Por item:
|
|||
| Decisão de NÃO fazer | Comentário/doc, não Issue |
|
||||
|
||||
2. **Explorar o código o mínimo** — só para apontar **direção** (rotas/pastas/domínios prováveis) e evitar duplicar algo já entregue. Não inventar signatures, collections novas como decisão travada, nem diagramas de componentes.
|
||||
3. **Superfície UI (A–D)** como dica para quem for executar — não semear brief Impeccable completo. Classe **B/C/D** (ou qualquer mudança de UI): criar **Cursor canvas** de rascunho UI/UX ([ui-draft-canvas.md](ui-draft-canvas.md) + skill `canvas`) **antes do gate**; ASCII no plano fica opcional. Classe **A** / sem UI: sem canvas.
|
||||
3. **Superfície UI (A–D)** como dica para quem for executar — não semear brief Impeccable completo. Classe **B/C/D** (ou qualquer mudança de UI): criar **rascunho HTML+Tailwind** ([ui-draft-html.md](ui-draft-html.md)) **antes do gate**, renderizar o PNG (`pnpm ui-draft:render`) e embutir no plano; ASCII no plano fica opcional. Classe **A** / sem UI: sem rascunho.
|
||||
4. **Dados (intenção)** ou `Dados: N/A`.
|
||||
5. **Posicionamento:** `P0..P3`, `depends`, appetite, janela eleitoral se relevante, `serializes` se tocar recurso compartilhado (ex. migrations) — sem detalhar a migration.
|
||||
6. **Plano de intenção** em `docs/plans/<slug>.md` via [intention-template.md](intention-template.md), com campo **Canvas UI** preenchido (path do `.canvas.tsx` ou `N/A`). Self-score ≥4/5 ([shaping.md](shaping.md)).
|
||||
6. **Plano de intenção** em `docs/plans/<slug>.md` via [intention-template.md](intention-template.md), com campo **Rascunho UI** preenchido (path do `.html` + PNGs embutidos, ou `N/A`). Self-score ≥4/5 ([shaping.md](shaping.md)).
|
||||
|
||||
### O que é proibido no plano de intenção
|
||||
|
||||
|
|
@ -113,7 +113,7 @@ Ordem topológica (dependente cita ID do dependido). Por item:
|
|||
- Appetite e fora de escopo (produto)
|
||||
- **Direção provável no codebase** (pastas/rotas/domínios — hipotética, revisável)
|
||||
- Questões em aberto com **Opções + Recomendação de produto** (não de engenharia)
|
||||
- **Se muda UI:** canvas de rascunho UI/UX no gate ([ui-draft-canvas.md](ui-draft-canvas.md)) + link no plano
|
||||
- **Se muda UI:** rascunho UI/UX (HTML+Tailwind → PNG) no gate ([ui-draft-html.md](ui-draft-html.md)) + PNG embutido no plano
|
||||
|
||||
## Passo 4 — Modelo × effort
|
||||
|
||||
|
|
@ -128,11 +128,11 @@ Skill `model-selection`. Registre no cabeçalho (`Model:`) e no gate.
|
|||
Antes de criar Issues **ou** abrir PR de planos:
|
||||
|
||||
- Overview: ID, título, prio, depends, appetite, modelo, link do plano local
|
||||
- **Para cada item que muda UI:** link markdown absoluto do canvas `plan-<id>-ui-draft.canvas.tsx` (abrir ao lado do chat) — este é o rascunho visual a validar
|
||||
- Esboço textual de fluxo só se ajudar; não substitui o canvas quando há UI
|
||||
- **Para cada item que muda UI:** mostre as imagens do rascunho (PNGs embutidos no plano) + link do `.html` fonte — este é o rascunho visual a validar
|
||||
- Esboço textual de fluxo só se ajudar; não substitui o rascunho quando há UI
|
||||
- Perguntas acumuladas numa rodada, recomendação de produto primeiro
|
||||
|
||||
**Pare e espere.** Itere (incluindo o canvas, se houver UI) até confirmação explícita do lote (não basta um “ok” solto durante a edição). Só então Passo 6.
|
||||
**Pare e espere.** Itere (incluindo o rascunho — HTML → re-render do PNG, se houver UI) até confirmação explícita do lote (não basta um “ok” solto durante a edição). Só então Passo 6.
|
||||
|
||||
## Passo 6 — Registro (não claimável até plano em `main`)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ Issue: #<N> (após `pnpm agent:register`; senão "—")
|
|||
Priority: <P0 | P1 | P2 | P3>
|
||||
Model: <slug model-selection>
|
||||
Impeccable: <A | B | C | D> — <N/A sem UI | encaixe em tela X | fluxo novo em Y>
|
||||
Canvas UI: <N/A — sem UI | path absoluto do plan-<id>-ui-draft.canvas.tsx>
|
||||
Rascunho UI: <N/A — sem UI | docs/plans/<slug>-ui-draft.html + PNG embutido abaixo>
|
||||
Appetite: <ex.: ~0,5–1 dia eng; um outcome verificável>
|
||||
Responsável: —
|
||||
|
||||
|
|
@ -30,13 +30,21 @@ Responsável: —
|
|||
|
||||
### Esboço de fluxo (B/C/D)
|
||||
|
||||
<!-- Opcional se houver Canvas UI. ASCII de jornada (backup textual), não layout de implementação.
|
||||
Omitir se A / sem UI. O artefato obrigatório do gate para UI é o Cursor canvas — ver ui-draft-canvas.md. -->
|
||||
<!-- Opcional se houver Rascunho UI. ASCII de jornada (backup textual), não layout de implementação.
|
||||
Omitir se A / sem UI. O artefato obrigatório do gate para UI é o PNG do rascunho HTML+Tailwind — ver ui-draft-html.md. -->
|
||||
|
||||
\```text
|
||||
[início] → … → [outcome]
|
||||
\```
|
||||
|
||||
### Rascunho UI (B/C/D)
|
||||
|
||||
<!-- Obrigatório se houver UI: PNG(s) renderizado(s) de docs/plans/<slug>-ui-draft.html
|
||||
(pnpm ui-draft:render) — este é o rascunho visual a validar no gate.
|
||||
Omitir se A / sem UI. O HTML é a fonte iterável; o PNG é o registro do aceite. -->
|
||||
|
||||

|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- <Outcome verificável em linguagem de produto, um por bullet>
|
||||
|
|
@ -79,7 +87,7 @@ Responsável: —
|
|||
## Referências
|
||||
|
||||
- GitHub Issue #<N>
|
||||
- Canvas UI (gate): <link/path do `.canvas.tsx` ou N/A>
|
||||
- Rascunho UI (gate): <link do `.html` + PNGs embutidos acima | N/A>
|
||||
- <arquivos/rotas úteis para o executor abrir primeiro — como pista, não contrato>
|
||||
- `AGENTS.md` / `docs/ARCHITECTURE.md` — só se o item toca convenção já travada no repo
|
||||
````
|
||||
|
|
@ -87,7 +95,7 @@ Responsável: —
|
|||
Notas:
|
||||
|
||||
- Alvo: ~60–100 linhas. Mais que isso costuma significar engenharia vazando para a intenção.
|
||||
- Classe **A:** `Impeccable: A — N/A`; `Canvas UI: N/A`; omita esboço de fluxo.
|
||||
- Classe **B/C/D:** canvas obrigatório no gate ([ui-draft-canvas.md](ui-draft-canvas.md)); leia a skill `canvas` ao escrever o `.canvas.tsx`.
|
||||
- Classe **A:** `Impeccable: A — N/A`; `Rascunho UI: N/A`; omita esboço de fluxo.
|
||||
- Classe **B/C/D:** rascunho HTML+Tailwind obrigatório no gate ([ui-draft-html.md](ui-draft-html.md)); renderize com `pnpm ui-draft:render` e embuta o PNG no plano.
|
||||
- Não rode `/impeccable` aqui.
|
||||
- Self-score shaping ≥4/5 antes de gravar.
|
||||
|
|
|
|||
|
|
@ -53,6 +53,6 @@ Armadilhas se alguém “só completar” o desejo humano:
|
|||
4. Direção no codebase é hipótese (não contrato técnico)?
|
||||
5. Zero decisões duras de engenharia no plano?
|
||||
|
||||
Se o item muda UI: canvas de rascunho no gate ([ui-draft-canvas.md](ui-draft-canvas.md)) antes de registrar — não conta como “engenharia no plano”; é validação visual da intenção.
|
||||
Se o item muda UI: rascunho HTML+Tailwind no gate ([ui-draft-html.md](ui-draft-html.md)) antes de registrar — não conta como “engenharia no plano”; é validação visual da intenção.
|
||||
|
||||
<4 → corrigir antes de gravar / registrar Issue.
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ No Teqo: **idea-refine / interview** antes se vago; **spec + plan + tasks** = es
|
|||
|
||||
`frontend-ui-engineering`, `security-and-hardening`, `performance-optimization`, `code-review-and-quality`, `code-simplification`, `observability-and-instrumentation`, `shipping-and-launch`, `ci-cd-and-automation`, `git-workflow-and-versioning`, `deprecation-and-migration`, `debugging-and-error-recovery`, `browser-testing-with-devtools` — entram em `work-issue` / gates do repo, não no registro da Issue.
|
||||
|
||||
**Exceção de superfície:** a skill Cursor **`canvas`** é usada no `plan-issue` só para o **rascunho UI/UX do gate** quando o item muda UI ([ui-draft-canvas.md](ui-draft-canvas.md)). Não é implementação de `/campanha` nem substituto de Impeccable.
|
||||
**Exceção de superfície:** o **rascunho UI/UX do gate** — HTML+Tailwind renderizado em PNG (`pnpm ui-draft:render`), [ui-draft-html.md](ui-draft-html.md) — é usado no `plan-issue` só quando o item muda UI. Não é implementação de `/campanha` nem substituto de Impeccable.
|
||||
|
||||
## Precedência quando conflita
|
||||
|
||||
|
|
|
|||
|
|
@ -1,54 +0,0 @@
|
|||
# Canvas de rascunho UI/UX (plan-issue)
|
||||
|
||||
Quando o item **muda UI** (classe Impeccable **B / C / D**, ou qualquer superfície que o usuário vê/toca), o gate **obrigatoriamente** apresenta um **Cursor canvas** com rascunho visual da intenção — não ASCII sozinho, não wireframe de implementação.
|
||||
|
||||
Leia e siga a skill `canvas` (`~/.cursor/skills-cursor/canvas/SKILL.md`) ao criar/editar o arquivo.
|
||||
|
||||
## Quando criar
|
||||
|
||||
| Situação | Canvas? |
|
||||
| -------- | ------- |
|
||||
| `Impeccable: A` / N/A sem UI / chore sem superfície | Não |
|
||||
| Encaixe em tela existente (B) | Sim — estado atual → estado desejado da área tocada |
|
||||
| Fluxo novo ou redesign local (C/D) | Sim — jornada + telas/estados chave |
|
||||
| Só copy/label sem rearranjo | Não (a menos que o humano peça ver o contexto) |
|
||||
|
||||
Um canvas por item com UI (ou um canvas do lote com secções por ID, se o lote for pequeno e as superfícies forem do mesmo fluxo). Preferir **um arquivo por item** (`plan-<id>-ui-draft.canvas.tsx`).
|
||||
|
||||
## Onde gravar
|
||||
|
||||
Path canônico do IDE (não no repo Teqo):
|
||||
|
||||
`/Users/<user>/.cursor/projects/<workspace>/canvases/plan-<id>-ui-draft.canvas.tsx`
|
||||
|
||||
Ex.: `plan-c42-ui-draft.canvas.tsx`. Escreva o arquivo com a tool de write; não peça `mkdir`. No chat do gate, linke o path absoluto em markdown.
|
||||
|
||||
## O que mostrar (produto)
|
||||
|
||||
Rascunho de **intenção de uso**, legível sozinho:
|
||||
|
||||
1. **Persona + job** (uma linha).
|
||||
2. **Jornada** (passos / estados) — o que a pessoa vê e decide.
|
||||
3. **Layout esquemático** das superfícies tocadas: zonas (lista, detalhe, formulário, mapa, empty, erro), hierarquia de atenção, CTAs primários/secundários.
|
||||
4. **Estados críticos** se mudarem o aceite (vazio, loading, sem permissão, fail-closed) — só os que importam ao produto.
|
||||
5. **Fora de escopo visual** em callout curto (o que este item *não* redesenha).
|
||||
|
||||
Use `Stack` / `Grid` / `Row` / `Card` / `Pill` / `Callout` / `Text` / `H1`–`H3` de `cursor/canvas`. Cores só via `useHostTheme()`. Pode usar caixas/`div` com bordas de token para simular regiões de tela — isso é rascunho de UX, não componente do app.
|
||||
|
||||
## O que é proibido no canvas
|
||||
|
||||
- Assinaturas, nomes de arquivos/componentes obrigatórios, schema, migrations
|
||||
- Brief Impeccable completo, craft/critique/polish, paleta/brand do produto Teqo
|
||||
- Mock com dados inventados como se fossem métricas reais (“No data”, KPIs zerados) — omita secções sem conteúdo
|
||||
- Gradientes, emojis, box-shadow, arco-íris (regras da skill canvas)
|
||||
- Travar o executor numa única forma técnica
|
||||
|
||||
## Relação com o plano em `docs/plans/`
|
||||
|
||||
- No plano: campo **Canvas UI:** link/path do `.canvas.tsx` (ou `N/A — sem UI`).
|
||||
- Esboço ASCII no markdown vira **opcional** (backup textual); o **canvas é o artefato do gate** para UI.
|
||||
- O canvas **não** é commitado no repo Teqo por default — é artefato de sessão do gate. Se o humano pedir arquivar no repo, só com pedido explícito (fora do fluxo padrão).
|
||||
|
||||
## Gate
|
||||
|
||||
Antes de `pnpm agent:register`, mostre o overview do lote **e** o(s) canvas de UI. Itere no canvas até o humano confirmar a intenção visual. Só então registre a Issue.
|
||||
80
.agents/skills/plan-issue/ui-draft-html.md
Normal file
80
.agents/skills/plan-issue/ui-draft-html.md
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
# Rascunho UI/UX do gate (plan-issue)
|
||||
|
||||
Quando o item **muda UI** (classe Impeccable **B / C / D**, ou qualquer superfície que o usuário vê/toca), o gate **obrigatoriamente** apresenta um **rascunho visual** da intenção — não ASCII sozinho, não wireframe de implementação. O meio é **HTML + Tailwind**, renderizado em PNG e **commitado no repo Teqo** junto do plano. (Substitui o antigo Cursor canvas, que vivia fora do repo em `~/.cursor/projects/<workspace>/canvases/`, só renderizava no IDE de quem planejou e apodrecia.)
|
||||
|
||||
## Para que serve — e para que NÃO serve
|
||||
|
||||
Serve para **colaborar visualmente com o humano** e fechar **layout, espaçamentos e tamanhos**: hierarquia, zonas, CTAs, densidade, medidas reais. É a linguagem do gate para "é isso que o usuário vê/toca".
|
||||
|
||||
**NÃO é** implementação da feature, **NÃO é** protótipo funcional, **NÃO é** entrega de app, **NÃO é** decisão de engenharia. Quem executa (`work-issue` / `agent-work-issue`) constrói a partir do plano; o rascunho é descartável como fonte e imutável como registro (o PNG aprovado).
|
||||
|
||||
## O que o rascunho DEVE ter (fidelidade mínima)
|
||||
|
||||
1. **Layout real** das superfícies tocadas: zonas (lista, detalhe, formulário, mapa, empty, erro), hierarquia de atenção, CTA primário vs secundário.
|
||||
2. **Espaçamentos e tamanhos reais** — a mesma escala do app (Tailwind v4: `p-4`, `gap-3`, `h-11`, `text-sm`, `rounded-lg`). **É disso que o gate discorda ou aprova.**
|
||||
3. **Breakpoints reais**: cena mobile (~390px) e cena desktop (~1280px) quando o fluxo tocar os dois. Um rascunho mobile sem viewport mobile não valida nada.
|
||||
4. **Estados críticos** se mudarem o aceite (vazio, loading, sem permissão, fail-closed) — como **cenas estáticas separadas**, nunca como interação.
|
||||
5. **Texto real pt-BR**; valores obviamente genéricos ("Nome da liderança", "12 municípios"); barras cinza para conteúdo desconhecido.
|
||||
|
||||
## O teto (proibido — é rascunho, não implementação)
|
||||
|
||||
1. **Zero funcionalidade.** Nenhum JS de comportamento: sem `onClick`, sem state, sem tabs/drawer/accordion de verdade, sem condicionais. Cada estado = uma cena estática. O único JS do arquivo é o build browser do Tailwind.
|
||||
2. **Zero código de app.** Não importar de `src/`, não escrever o componente final, não nomear componentes/arquivos obrigatórios. O rascunho é **descartável**: o executor constrói do plano, não copia o rascunho.
|
||||
3. **Zero brand/polish.** Sem gradientes, emojis, box-shadow, animações, microinteractions, logo/imagens reais, paleta do produto. Cinza neutro (zinc/neutral) + **um único acento** para o CTA primário. Fonte do sistema.
|
||||
4. **Zero mock de dados.** Sem KPIs realistas, nomes reais, números de votos — placeholders óbvios ou barras cinza.
|
||||
5. **Zero decisão de engenharia.** Sem schema, migration, nomes de arquivos obrigatórios, assinaturas — o rascunho mostra superfície, não solução.
|
||||
|
||||
## Como fazer (artefatos no repo)
|
||||
|
||||
1. **Escreva** `docs/plans/<slug>-ui-draft.html` — um arquivo por item, autossuficiente:
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<title>UI draft — <título do item></title>
|
||||
</head>
|
||||
<body class="bg-zinc-950 text-zinc-100 antialiased">
|
||||
<main class="mx-auto max-w-5xl p-8">
|
||||
<p class="text-sm text-zinc-400">Persona/job em uma linha + cenas abaixo</p>
|
||||
<section data-shot="desktop" class="mt-6">…</section>
|
||||
<section data-shot="mobile" class="mt-6 w-[390px]">…</section>
|
||||
<section data-shot="vazio" class="mt-6">…</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
- Tailwind **v4** (`@tailwindcss/browser@4`) — mesma major do app (4.1.x). Requer rede apenas na hora de renderizar, nunca no artefato.
|
||||
- **Cada cena que vira imagem: `<section data-shot="<rótulo>">`** (rótulo kebab, ex. `mobile`, `desktop-vazio`). Sem `data-shot`, o script captura a página inteira.
|
||||
- O arquivo **é commitado** com o plano: o rascunho aprovado vira registro permanente do aceite (o PR revisa HTML + PNG; ninguém depende do IDE de quem planejou).
|
||||
|
||||
2. **Renderize:**
|
||||
|
||||
```bash
|
||||
pnpm ui-draft:render docs/plans/<slug>-ui-draft.html
|
||||
```
|
||||
|
||||
→ `docs/plans/<slug>-ui-draft-<rótulo>.png` (uma por cena `data-shot`) ou `docs/plans/<slug>-ui-draft.png` (full-page). Usa o Chromium do Playwright já instalado no repo; nunca toca no banco.
|
||||
|
||||
3. **Embute no plano e commite:**
|
||||
|
||||
```markdown
|
||||

|
||||
```
|
||||
|
||||
## Relação com o plano em `docs/plans/`
|
||||
|
||||
- Campo **Rascunho UI:** no cabeçalho: `docs/plans/<slug>-ui-draft.html` (ou `N/A — sem UI`).
|
||||
- PNGs embutidos no corpo do plano (secção "Rascunho UI (gate)").
|
||||
- Esboço ASCII no markdown vira **opcional** (backup textual); o PNG é o artefato do gate.
|
||||
- **É commitado com o plano** (muda o default antigo de "artefato de sessão, fora do repo"): o PNG aprovado é o registro do aceite visual e viaja com a Issue.
|
||||
|
||||
## Gate
|
||||
|
||||
Antes de `pnpm agent:register`, mostre o overview do lote **e** as imagens do rascunho (PNGs embutidos no plano — o humano vê no chat e no PR) + link do `.html` fonte. Itere: **editar HTML → re-renderizar (`pnpm ui-draft:render`) → atualizar PNGs** até confirmação explícita. Só então registre a Issue.
|
||||
|
||||
Legado: os antigos `plan-<id>-ui-draft.canvas.tsx` (Cursor) continuam válidos como histórico de planos passados — nenhum rascunho novo usa canvas.
|
||||
|
|
@ -4,6 +4,8 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente
|
|||
|
||||
---
|
||||
|
||||
**Recently resolved (2026-08-11):** **Plan-issue — rascunho UI/UX do gate vira HTML+Tailwind commitado no repo (ad-hoc, decisão do usuário).** O artefato deixa de ser **Cursor canvas** (`~/.cursor/projects/<workspace>/canvases/plan-<id>-ui-draft.canvas.tsx`, fora do repo, só renderizava no IDE de quem planejou e apodrecia) e passa a ser **HTML + Tailwind v4** (`docs/plans/<slug>-ui-draft.html` — `@tailwindcss/browser@4` via jsdelivr, mesma major do app 4.1.x; rede só na hora de renderizar) **commitado com o plano** e **renderizado em PNG** por `pnpm ui-draft:render` (`scripts/render-ui-draft.mjs`, Chromium do Playwright via `@playwright/test`, fallback `load` se o CDN não settle — nunca toca banco/app): uma PNG por cena `<section data-shot="<rótulo>">` (ex. `mobile`, `desktop-vazio`) → `docs/plans/<slug>-ui-draft-<rótulo>.png`, ou full-page sem `data-shot`; PNGs **embutidos no plano** (visíveis no chat do gate E no PR — o aceite vira registro permanente; o HTML é a fonte iterável). **Contrato de fidelidade explícito em `ui-draft-html.md`** (substitui `ui-draft-canvas.md`, deletado; refs atualizadas em SKILL.md/frontmatter, intention-template.md — campo renomeado `Canvas UI:` → `Rascunho UI:` + secção de embed, skills-map.md, shaping.md): o rascunho DEVE ter layout real, espaçamentos/tamanhos na escala do app (`p-4`, `gap-3`, `h-11`, `text-sm`), breakpoints reais (390/1280) e estados críticos como cenas estáticas; é PROIBIDO ser funcional (zero JS de comportamento — cada estado é cena separada), usar código de app (`src/`), brand/polish (gradientes/emojis/sombras/logo/paleta do produto — cinza neutro + um acento), mock de dados realistas ou decisão de engenharia. Gate: iterar HTML → re-render → embutir até confirmação explícita. Verificado: render real do skeleton (classes v4 aplicadas — oklch zinc, rounded-lg 8px, text-lg 18px). Sem migration/access/Consent/UI de produto/schema. Sem issue — ad-hoc no worktree `work/5`.
|
||||
|
||||
**Recently resolved (2026-08-11):** **C124** (Issue #668) — tarefa de atividade fica só com **texto, responsável e concluída**: o campo `due` (Prazo, datetime) sai do modelo e da UI. Remoção em cadeia num único commit simétrico: campo do array `tasks` da `activity` (o guardrail de liderança perde a comparação de `due` no mesmo item — continua fail-closed sobre título/responsável/contagem), `taskSchema` zod, `parseTasksFormData`/`ParsedActivityTask` (formData), `ActivityFormTaskViewModel`/`ActivityTaskViewModel`, input `datetime-local` do `ActivityTaskFields` (grid `2fr_1fr`) e linha "Prazo: …" do `ActivityTaskChecklist` (subtítulo condicional + `break-words` no título — P2 dos reviewers). `doneAt` ("concluída em") permanece como derivado invisível; `taskTotal`/`taskDoneCount` intocados. **Migration** `20260811_183016_drop_activity_task_due` (gerada; diff do gerador tinha no-op de `google_calendar_sync` por snapshot stale — limpa à mão para só o `DROP COLUMN "due"` de `activity_tasks`; dados descartados conforme decidido no gate). Grep pós: zero `due` fora das migrations (histórico frozen); nenhum consumidor fora da vertical (notificações, Google sync, quick actions). Gates: unit+int 736/736, e2e atividade 9/9 (1 flake ECONNRESET no run paralelo — rerun limpo), build OK. Intenção [`atividades-tarefas-sem-prazo.md`](plans/atividades-tarefas-sem-prazo.md); impl [`atividades-tarefas-sem-prazo-impl.md`](plans/atividades-tarefas-sem-prazo-impl.md).
|
||||
**Recently resolved (2026-08-11):** **C118** (Issue #657) — página de detalhe da pessoa `/campanha/pessoas/[id]` (staff), montada pelas **capacidades** da ficha: a mesma rota serve liderança, dobradinha, assessora, apoiador — e só mostra o que a pessoa tem. **Loader irmão** `loadPersonDetail` (`src/utilities/people/personDetail.ts`) reusa o merge da lista (`mergePeopleSources` + `scopePeopleRows`) com os mesmos selects por fonte — o **escopo do assessor é literalmente o da lista** (pessoa só aparece se alguma capacidade municipal estiver na carteira; `null` → `notFound()`), staff com bypass justificado (identity-gated, prova pelo merge); **seção Apoiador** (dado novo vs. view model da lista) lê `supporter` por contact com o access da própria collection (`overrideAccess: false`) — camada dupla, nunca alarga (int testado: apoiador de município fora da carteira é cortado). **`[id]` é `contactID`** (a chave de linha da C100 — e o link do Nome na lista, que a C116 nasceu apontando para `/campanha/liderancas/[id]`, agora aponta para cá). UI: `PersonDetailSections` em `components/campaign/people/` — Ficha (partido/telefone/e-mail/base + pills de capacidade), Liderança (status, acesso ao app, municípios, link "Abrir detalhe de liderança" — convivência v1 com `/campanha/liderancas/[id]`, votos continuam lá), Dobradinha, Assessora (por conta, `campaignRoleLabels`), Assessorado, Apoiador (fonte · município · intenção quando consentida · data), Ações reusando os botões da lista (WhatsApp, `LeadershipInviteRowAction` só com liderança, `DeletePersonButton` ganhou `deletedHref` opcional → volta à lista pós-delete). Sem migration/access novo/Consent; sem votos/KPI (intenção corta; assimetria intacta). Testes: int `personDetail` (matriz de escopo: unrestricted completo, assessor na carteira vê, fora → null, corte de supporter, só-staff só para unrestricted, conta leader não acende Assessora, contato solto/leader → null) e e2e novos em `campaignPeople` (coordinator vê seções por capacidade + link de liderança; dobradinha+apoiador sem bloco de liderança; leader → `/campanha/contatos`). Verificado ao vivo em dev (coordenador/assessor/leader, 404 de id inválido, mobile). Intenção [`pessoas-detalhe-por-capacidades.md`](plans/pessoas-detalhe-por-capacidades.md); impl [`pessoas-detalhe-por-capacidades-impl.md`](plans/pessoas-detalhe-por-capacidades-impl.md).
|
||||
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@
|
|||
"test:e2e:affected": "node scripts/run-e2e-affected.mjs",
|
||||
"test:int": "cross-env NODE_OPTIONS=--no-deprecation vitest run --config ./vitest.config.mts",
|
||||
"test:unit": "cross-env NODE_OPTIONS=--no-deprecation DATABASE_URL=postgresql://invalid:invalid@127.0.0.1:1/unit_tests_must_not_connect vitest run --config ./vitest.unit.config.mts",
|
||||
"ui-draft:render": "node scripts/render-ui-draft.mjs",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"typecheck:tsc6": "tsc6 --noEmit",
|
||||
"prepare": "husky",
|
||||
|
|
|
|||
52
scripts/render-ui-draft.mjs
Normal file
52
scripts/render-ui-draft.mjs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* Renders a plan-issue UI draft (HTML + Tailwind) to PNG(s) for the gate.
|
||||
*
|
||||
* Usage: pnpm ui-draft:render <docs/plans/<slug>-ui-draft.html>
|
||||
*
|
||||
* - Screenshots each `[data-shot]` scene as `<slug>-ui-draft-<label>.png`
|
||||
* (label = the data-shot value, e.g. `mobile`, `desktop-vazio`).
|
||||
* - With no `data-shot` scenes, captures the full page as `<slug>-ui-draft.png`.
|
||||
* - Uses the Chromium already installed for Playwright tests. The draft's
|
||||
* Tailwind build comes from a CDN (jsdelivr), so rendering needs network
|
||||
* access. Never touches the database or the app code.
|
||||
*/
|
||||
|
||||
import { chromium } from '@playwright/test'
|
||||
import { existsSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
const htmlPath = path.resolve(process.argv[2] ?? '')
|
||||
|
||||
if (!htmlPath.endsWith('.html') || !existsSync(htmlPath)) {
|
||||
console.error('Usage: pnpm ui-draft:render <docs/plans/<slug>-ui-draft.html>')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const base = htmlPath.replace(/\.html$/, '')
|
||||
const browser = await chromium.launch()
|
||||
try {
|
||||
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } })
|
||||
await page
|
||||
.goto(`file://${htmlPath}`, { waitUntil: 'networkidle', timeout: 30000 })
|
||||
.catch(() => page.goto(`file://${htmlPath}`, { waitUntil: 'load' }))
|
||||
await page.waitForTimeout(250)
|
||||
|
||||
const scenes = await page.locator('[data-shot]').all()
|
||||
const files = []
|
||||
if (scenes.length > 0) {
|
||||
for (let i = 0; i < scenes.length; i += 1) {
|
||||
const label = (await scenes[i].getAttribute('data-shot'))?.trim() || String(i + 1)
|
||||
const safeLabel = label.replace(/[^a-z0-9-]+/gi, '-').replace(/^-+|-+$/g, '') || String(i + 1)
|
||||
const out = `${base}-${safeLabel}.png`
|
||||
await scenes[i].screenshot({ path: out })
|
||||
files.push(out)
|
||||
}
|
||||
} else {
|
||||
const out = `${base}.png`
|
||||
await page.screenshot({ path: out, fullPage: true })
|
||||
files.push(out)
|
||||
}
|
||||
for (const file of files) console.log(file)
|
||||
} finally {
|
||||
await browser.close()
|
||||
}
|
||||
Loading…
Reference in a new issue