@snksergio/design-system 0.60.0 → 0.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist-lib/ai/componentes/AlertModal.md +47 -0
  2. package/dist-lib/ai/componentes/AppShell.md +117 -0
  3. package/dist-lib/ai/componentes/Breadcrumb.md +187 -0
  4. package/dist-lib/ai/componentes/Button.md +116 -0
  5. package/dist-lib/ai/componentes/ButtonGroup.md +162 -0
  6. package/dist-lib/ai/componentes/CardCheckbox.md +99 -0
  7. package/dist-lib/ai/componentes/CardOption.md +133 -0
  8. package/dist-lib/ai/componentes/Chart.md +93 -0
  9. package/dist-lib/ai/componentes/Chip.md +68 -0
  10. package/dist-lib/ai/componentes/ChoroplethMap.md +118 -0
  11. package/dist-lib/ai/componentes/ColorPicker.md +70 -0
  12. package/dist-lib/ai/componentes/Combobox.md +51 -0
  13. package/dist-lib/ai/componentes/ConversationListItem.md +90 -0
  14. package/dist-lib/ai/componentes/DataList.md +111 -0
  15. package/dist-lib/ai/componentes/DataTable.md +867 -0
  16. package/dist-lib/ai/componentes/DatePicker.md +84 -0
  17. package/dist-lib/ai/componentes/DateSeparatorChip.md +59 -0
  18. package/dist-lib/ai/componentes/EmptyState.md +72 -0
  19. package/dist-lib/ai/componentes/FileUploadField.md +95 -0
  20. package/dist-lib/ai/componentes/FloatingPanel.md +118 -0
  21. package/dist-lib/ai/componentes/FooterTable.md +62 -0
  22. package/dist-lib/ai/componentes/FormField.md +110 -0
  23. package/dist-lib/ai/componentes/Gantt.md +552 -0
  24. package/dist-lib/ai/componentes/Header.md +98 -0
  25. package/dist-lib/ai/componentes/Icon.md +65 -0
  26. package/dist-lib/ai/componentes/Kanban.md +343 -0
  27. package/dist-lib/ai/componentes/Kpi.md +103 -0
  28. package/dist-lib/ai/componentes/List.md +61 -0
  29. package/dist-lib/ai/componentes/MarkdownText.md +59 -0
  30. package/dist-lib/ai/componentes/MenuSidebar.md +128 -0
  31. package/dist-lib/ai/componentes/MessageAck.md +53 -0
  32. package/dist-lib/ai/componentes/MessageBubble.md +115 -0
  33. package/dist-lib/ai/componentes/MessageComposer.md +80 -0
  34. package/dist-lib/ai/componentes/MessageVariablesPicker.md +104 -0
  35. package/dist-lib/ai/componentes/Modal.md +88 -0
  36. package/dist-lib/ai/componentes/MonthYearPicker.md +49 -0
  37. package/dist-lib/ai/componentes/PageHeader.md +129 -0
  38. package/dist-lib/ai/componentes/Panel.md +84 -0
  39. package/dist-lib/ai/componentes/Scheduler.md +421 -0
  40. package/dist-lib/ai/componentes/ScreenLoader.md +60 -0
  41. package/dist-lib/ai/componentes/SingleMenuSidebar.md +171 -0
  42. package/dist-lib/ai/componentes/Spinner.md +52 -0
  43. package/dist-lib/ai/componentes/Table.md +192 -0
  44. package/dist-lib/ai/componentes/TableToolbar.md +87 -0
  45. package/dist-lib/ai/componentes/TabsNavigation.md +152 -0
  46. package/dist-lib/ai/componentes/Toast.md +49 -0
  47. package/dist-lib/ai/componentes/_primitivos.md +74 -0
  48. package/dist-lib/ai/componentes/avatar-ig.md +181 -0
  49. package/dist-lib/ai/componentes/indice.json +49 -0
  50. package/dist-lib/ai/exemplos/dashboard/dashboard-brazil-map.ts +33 -0
  51. package/dist-lib/ai/exemplos/dashboard/dashboard-screen.tsx +1110 -0
  52. package/dist-lib/ai/exemplos/dashboard/index.ts +1 -0
  53. package/dist-lib/ai/global/componentes.md +217 -0
  54. package/dist-lib/ai/global/composicao.md +182 -0
  55. package/dist-lib/ai/indice.json +177 -0
  56. package/dist-lib/ai/lint/ds-lint-patterns.mjs +115 -0
  57. package/dist-lib/ai/manifest.json +16 -0
  58. package/dist-lib/ai/regras/design.md +88 -0
  59. package/dist-lib/ai/regras/temas.md +192 -0
  60. package/dist-lib/ai/regras-por-componente.json +103 -0
  61. package/dist-lib/ai/roteiros/dashboard/blueprint.md +47 -0
  62. package/dist-lib/ai/roteiros/dashboard/entrevista.md +62 -0
  63. package/dist-lib/ai/roteiros/dashboard/geracao.md +88 -0
  64. package/dist-lib/ai/roteiros/dashboard/roteiro.md +88 -0
  65. package/package.json +4 -1
@@ -0,0 +1,115 @@
1
+ /**
2
+ * ds-lint-patterns — FONTE ÚNICA dos anti-patterns de estilo do DS.
3
+ *
4
+ * Consumido por DOIS clientes (nunca duplique a tabela):
5
+ * - .claude/hooks/ds-lint-styles.sh → aviso local, nunca bloqueia
6
+ * - scripts/lint-styles.mjs --ratchet → check de CI, bloqueia violação nova
7
+ *
8
+ * ⚠️ E por um TERCEIRO, fora deste repo: este arquivo é foundational
9
+ * (`foundational-pairs.mjs`) e é copiado inteiro para
10
+ * `cli/templates/default/_claude/hooks/`, que o ds:link projeta no projeto do
11
+ * consumidor. Lá, `.ai/` e os caminhos internos do DS NÃO EXISTEM — os que
12
+ * aparecem nos comentários abaixo só resolvem aqui. Por isso nenhuma `msg`
13
+ * cita caminho: mensagem de lint tem que se bastar onde for lida.
14
+ *
15
+ * ⚠️ Só entram aqui regras erradas INDEPENDENTE DE CONTEXTO (valor divergente
16
+ * do token, classe que não existe). Regras que exigem contexto cross-elemento
17
+ * ou julgamento de intenção — L-004 (afordância de foco pode estar no wrapper)
18
+ * e L-007 (escolha de preset tipográfico) — pertencem ao revisor semântico,
19
+ * NÃO a este arquivo. Ver `.ai/specs/pipeline-governance-ci.md` §1.1.
20
+ *
21
+ * Buracos de cobertura conhecidos (não fechados nesta rodada — cada um exige
22
+ * medição/decisão de política própria; ver it.todo no teste):
23
+ * - `space-x-N` / `space-y-N` (utility legado, sem token DS direto) e
24
+ * `w-N`/`h-N` isolados fora das famílias `h|min-h|size` acima.
25
+ * - **Template literals**: o delimitador cobre aspas simples e duplas, mas
26
+ * NÃO crase — `` `flex gap-4` `` passa limpo (forma diferente, não
27
+ * coberta nesta rodada).
28
+ * - **Números fora do que foi medido no baseline**: as alternações listam
29
+ * só os valores encontrados na varredura de 2026-07-29, não a escala
30
+ * Tailwind inteira — `w-10`, `p-9`, `gap-11`, `h-20` passam limpo hoje.
31
+ * Ampliar a faixa numérica exige nova medição própria, fora desta correção.
32
+ */
33
+
34
+ export const DS_LINT_PATTERNS = [
35
+ {
36
+ id: "L-001",
37
+ re: /ring-ring-[a-z-]+\/[0-9]+/,
38
+ msg: "ring-ring-*/N — o token de ring já tem alpha embutido. Remova o /N.",
39
+ },
40
+ // `0` fora da alternação de propósito: não existe token DS pra zero
41
+ // (p-0/gap-0 são resets legítimos, comuns com `!` sobre base do shadcn).
42
+ {
43
+ id: "L-002",
44
+ re: /['"][^'"]*\bgap(-[xy])?-(1|2|3|4|5|6|7|8|9|10|12|14|16|20|24)\b[^'"]*['"]/,
45
+ msg: "gap-N literal → use gap-gp-{2xs,xs,sm,md,lg,xl,2xl}.",
46
+ },
47
+ {
48
+ id: "L-002",
49
+ re: /['"][^'"]*\b(px|py|pt|pb|pl|pr|p)-(1|2|3|4|5|6|7|8|10|12|16)\b[^'"]*['"]/,
50
+ msg: "pad/space literal → use p-sp-* (space) ou px-pad-* (pad).",
51
+ },
52
+ // ⚠️ Esta msg NÃO aponta pra arquivo, e é de propósito. Ela terminava com
53
+ // "ver .ai/context/tokens/sizing-shape-elevation.md" — caminho que existe aqui
54
+ // e que o CONSUMIDOR nunca recebe: este módulo é foundational (ver
55
+ // foundational-pairs.mjs) e roda também no `.claude/` projetado pelo ds:link,
56
+ // onde `.ai/` não existe. Mensagem que manda ler o inalcançável faz a pessoa
57
+ // parar de investigar achando que a resposta está em outro lugar (L-060).
58
+ // A regra aqui é: msg de lint se basta ou não cita caminho.
59
+ {
60
+ id: "L-002",
61
+ re: /['"][^'"]*\b(h|min-h|size)-(7|8|9|10|11|12|13|14|16)\b[^'"]*['"]/,
62
+ msg: "height/size fixo → use min-h-form-* (h-9=form-md, h-10=form-lg, h-11=form-xl). Se for quadrado, size-comp-*. Se for maior (~h-12 a h-16, 48-64px), não é form — use token de layout (ex.: h-layout-navbar).",
63
+ },
64
+ // `none` e `full` fora da alternação: são numericamente IDÊNTICOS ao token DS
65
+ // (--radius-radius-full: 9999px, --radius-radius-none: 0px) → não podem ser
66
+ // defeito. Já sm..3xl DIVERGEM (nativo lg=0.5rem vs DS 0.625rem) → defeito
67
+ // real. Side variants (rounded-t-lg) carregam o mesmo valor divergente.
68
+ {
69
+ id: "L-002",
70
+ re: /['"][^'"]*\brounded(-(t|b|l|r|tl|tr|bl|br|s|e|ss|se|es|ee))?-(sm|md|lg|xl|2xl|3xl)\b[^'"]*['"]/,
71
+ msg: "rounded-N nativo tem VALOR DIFERENTE do token DS (nativo lg=0.5rem vs DS 0.625rem) → use rounded-radius-*.",
72
+ },
73
+ {
74
+ id: "L-002",
75
+ re: /['"][^'"]*\bshadow-(2xs|xs|sm|md|lg|xl|2xl)\b[^'"]*['"]/,
76
+ msg: "shadow-N nativo → use shadow-sh-*.",
77
+ },
78
+ {
79
+ id: "L-003",
80
+ re: /['"][^'"]*\bring-3\b[^'"]*['"]/,
81
+ msg: "ring-3 não existe no Tailwind (vira no-op silencioso) → use ring-4.",
82
+ },
83
+ {
84
+ id: "L-005",
85
+ re: /['"][^'"]*\bbg-input\/[0-9]+[^'"]*['"]/,
86
+ msg: "bg-input/N é var do shadcn → use o token DS bg-bg-surface (ou bg-bg-muted).",
87
+ },
88
+ {
89
+ id: "IMPORT",
90
+ re: /from\s+['"]tailwind-variants['"]/,
91
+ msg: 'import errado: use `import { tv } from "@/utils/tv"` — o wrapper do DS carrega o twMergeConfig da L-016.',
92
+ },
93
+ ];
94
+
95
+ /** Linha de comentário não é código — citar uma classe proibida ao explicar a
96
+ * regra não pode reprovar o CI. Cobre `//`, `/*`, ` *` e o comentário JSX
97
+ * `{/* ... *\/}`, que entrou no escopo junto com o `.tsx`. */
98
+ const isComment = (text) => /^\s*(\/\/|\/\*|\*|\{\s*\/\*)/.test(text);
99
+
100
+ /**
101
+ * Roda todos os patterns contra uma lista de linhas.
102
+ * @param {Array<{n: number, text: string}>} lines
103
+ * @returns {Array<{id: string, msg: string, n: number, text: string}>}
104
+ */
105
+ export function scanLines(lines) {
106
+ const out = [];
107
+ for (const { n, text } of lines) {
108
+ if (isComment(text)) continue;
109
+ for (const p of DS_LINT_PATTERNS) {
110
+ // `re` sem flag /g → .test() não mantém lastIndex, seguro reusar.
111
+ if (p.re.test(text)) out.push({ id: p.id, msg: p.msg, n, text });
112
+ }
113
+ }
114
+ return out;
115
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "pacote": "@snksergio/design-system",
3
+ "versao": "0.62.0",
4
+ "commit": "7cd6db7",
5
+ "gerado": "2026-09-15",
6
+ "conteudo": {
7
+ "roteiros": [
8
+ "dashboard"
9
+ ],
10
+ "componentes": 47,
11
+ "componentesComRegra": 19,
12
+ "rotas": 19,
13
+ "arquivosDeExemplo": 3,
14
+ "residuoClaudeCode": 8
15
+ }
16
+ }
@@ -0,0 +1,88 @@
1
+
2
+ # Regras de design iGreen (auto-carregadas)
3
+
4
+ Ao gerar ou editar QUALQUER UI neste projeto, aplique sem ser pedido. Detalhe e
5
+ contexto em `DESIGN.md` (raiz). API de cada componente em
6
+ `src/components/ui/<Nome>/USAGE.md`.
7
+
8
+ > **Modo submódulo.** Se existe `.claude/ds-config.json` com `"mode": "submodule"`, três
9
+ > coisas mudam nesta regra: (1) `DESIGN.md` e os `USAGE.md` ficam em `<dsPath>/` e
10
+ > `<dsPath>/src/components/ui/<Nome>/`, não na sua raiz; (2) **não** existe `igreen:add`,
11
+ > manifesto nem hook de proteção — os componentes estão no disco e você importa pelo
12
+ > `importBase`; (3) a regra de **não editar o tema/fundação** continua valendo, só que por
13
+ > disciplina, não por bloqueio: o que você editar no submódulo some no próximo `git pull`.
14
+
15
+ ## Composição de tela
16
+ - Wrapper de página: `flex flex-col h-full min-h-0 gap-gp-2xl`.
17
+ - **16px (`gap-gp-2xl`) entre o `PageHeader` e o próximo bloco** — nunca grudado.
18
+ - Conteúdo que precisa preencher (tabela): `className="flex-1 min-h-0"` + pai com altura.
19
+ - Forms: `<FormField>` (nunca `<label>` cru) + `gap-form-gap` (20px) entre campos.
20
+ - Card: densidade pela prop `size` do `<Card>` — `sm` 16 · **`md` 20 (default)** · `lg` 24 (tokens `p-pad-card-sm/md/lg`; `base` é alias depreciado de `md`); entre cards `gap-gp-md`/`gap-gp-lg`.
21
+
22
+ ## Tokens (classe DS antes de Tailwind literal)
23
+ ```
24
+ gap-4 → gap-gp-md p-4 → p-sp-md/p-pad-2xl rounded-lg → rounded-radius-lg
25
+ shadow-md → shadow-sh-md h-9/h-10 → min-h-form-md/lg size-5 → size-icon-md
26
+ ```
27
+ - **Prefira o token de COMPONENTE ao genérico**: altura `min-h-form-*` (não `h-9/10`), gap de form `gap-form-gap`, ícone `size-icon-*` (não `size-5`), padding de card `p-pad-card-*`, gutter `px-pad-page-*`, chrome `h-layout-*`, largura `max-w-*`. Genérico (`gap-gp-*`/`p-sp-*`/`px-pad-*`) só como fallback. Cheat-sheet completo em `DESIGN.md` ("Tokens de componente").
28
+ - Foco: `focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-ring-{color}` (ring já tem alpha — nunca `/30`, nunca `ring-3`).
29
+ - Cor só por token semântico (`bg-bg-brand`, `text-fg-default`...). Zero hex em className. Destrutivo na API = `color="critical"`.
30
+
31
+ - ⚠️ **`max-w-container-*` NÃO existe** — `container` é o único namespace que não dobra o
32
+ prefixo. `max-w-md` já é os **768px do DS** (não os 448px do Tailwind); use `max-w-lg`,
33
+ `max-w-drawer-md`, `max-w-modal-sm`. A forma com `container` não emite CSS e some em silêncio.
34
+ - ⚠️ **`bg-bg-scrollbar-thumb` / `-hover` são de uso interno** dos `@utility scrollbar-*`
35
+ (alpha neutro, pra barra ter contraste próprio). Não use como fundo de elemento.
36
+
37
+ ## Tipografia
38
+ - Default interativo: `text-body-sm` (13/500). **7 papéis**: display/heading/title/body/caption/**stat**/code.
39
+ - **Número de KPI/métrica = `text-stat-{sm,md,lg,xl}`** (20/24/30/34px) + `tabular-nums`. Nunca `text-[Npx]` na unha, nunca `display-*`/`heading-*` pra valor de indicador.
40
+ - **Código / identificador / valor técnico = `text-code-{sm,md}`** (13/14px, Geist Mono) — chave de env, ID, hash, slug, path. Nunca `font-mono text-body-*` + `tracking-*` na unha: `code` é 1 dos 7 papéis e existe pra isto.
41
+ - Override de peso via `font-bold/semibold/medium/normal`. Nunca `text-xs font-semibold` avulso → use preset.
42
+
43
+ ## ⛔ O tema já traz o runtime — não redeclare
44
+ O `tailwind-theme.css` importado no seu CSS de entrada **não é só CSS vars**. Ele traz
45
+ `@font-face` do Geist, `--font-sans`/`--font-mono`, `@custom-variant dark`, regras de
46
+ `html`/`body`/`button` e as utilities `outline-float` e `scrollbar-thin`/`scrollbar-default`.
47
+
48
+ **Não redeclare nenhuma delas** no seu CSS: classe comum vence `@utility`, e a **segunda**
49
+ declaração de `@custom-variant` vence a primeira — você acaba com um comportamento no seu
50
+ projeto e outro no resto do sistema, sem erro nenhum. Os `.woff2` do Geist, sim, são seus
51
+ (o `@font-face` aponta pra `/fonts/`, raiz do site).
52
+
53
+ ## 🖼️ Trabalhando a partir de print / Figma / tela existente
54
+ A referência decide **conteúdo**; o DS decide **pele e comportamento**.
55
+ - **Dela**: copy, labels, nomenclatura, ordem dos campos. Copie e não "melhore" sem pedir — o jargão é do domínio do usuário.
56
+ - **Do DS, sempre**: fonte/família, tamanho, peso, tracking, cor, espaçamento, radius, sombra, foco e **comportamento de componente**. Preset existe → use o preset; compor na unha porque "ficou igual ao print" é drift.
57
+ - **Cor**: a referência escolhe o CONJUNTO (print escuro → `dark`; outra marca → `data-theme`), **nunca** o valor. Não derive hex do pixel.
58
+ - **Chrome (rail/menu/header) na referência = shell no escopo.** Print de app + pedido de "uma tela" é `shell + tela`. Não vai entregar o shell? Avise em linha destacada **antes** de gerar.
59
+
60
+ ## Antes de criar
61
+ - Existe exemplo/skill pra isso? (tabela→`/ds-create-crud`; ver `DESIGN.md` mapa de intenção). Puxe e adapte em vez de escrever do zero.
62
+ - Leu o `USAGE.md` do componente? Não invente prop/variante.
63
+ - `npx tsc --noEmit` limpo antes de entregar.
64
+
65
+ ## ⛔ Arquivos protegidos — NÃO editar (integridade do DS)
66
+ - **NUNCA edite** o tema/tokens (`src/styles/theme/**`) nem a fundação (`src/lib/utils.ts` = cn, `src/utils/tv.ts` = tv, `src/lib/lucide-types.ts`). São a base visual gerada pelo DS — editar quebra o sistema todo e some no próximo update.
67
+
68
+ > **O que de fato te impede, por canal:**
69
+ >
70
+ > | canal | trava |
71
+ > |---|---|
72
+ > | copy-in / scaffold | ✅ hook `protect-ds.mjs` **bloqueia** (`Edit`/`Write`/`MultiEdit`) e **avisa** em `Bash` que escreve nesses paths |
73
+ > | **submódulo** | ✅ **desde o CLI 0.24.0** — o `ds-link` projeta o hook, e ele reconhece o layout de submódulo (bloqueia `<dsPath>/src/styles/theme/**` e `<dsPath>/tokens/**`, sem confundir com o **seu** `src/`). ⚠️ **1 passo manual:** hook só roda se estiver no seu `.claude/settings.json`, e o `ds-link` não escreve nesse arquivo (é seu) — ele **imprime o bloco pronto** e repete o aviso até você colar |
74
+ > | npm install | ❌ nenhuma — o código vive em `node_modules` |
75
+ >
76
+ > ⚠️ **Até o CLI 0.25.1 esta tabela dizia "❌ nenhuma" no submódulo**, justificando com *"o
77
+ > `ds-link` não projeta `hooks/`"*. Era verdade até a 0.23.x e **deixou de ser na 0.24.0** —
78
+ > eu mudei o mecanismo e atualizei a `ds-channels.md`, mas não procurei as outras superfícies
79
+ > que descreviam a mesma coisa. Se você leu a versão anterior e concluiu que no submódulo não
80
+ > há rede de segurança: há, e vale colar o bloco.
81
+ - **Não edite o `.styles.ts`/internals de um componente do DS** pra "ajustar visual" de uma tela. Isso vira edição local (drift) e diverge do padrão. **Customize na COMPOSIÇÃO**: escolha variantes/props do componente + classes DS na SUA tela.
82
+ - Quer outra cor/tom? Use o **token/variante semântico** que já existe (`color="..."`, `bg-bg-*`). Não invente hex nem reescreva o token.
83
+ - Pra evoluir o tema de fato → re-sincronize com o DS (`npm run igreen:add -- theme`), não edite à mão.
84
+
85
+ ## Puxar componentes/exemplos (manifesto)
86
+ - **SEMPRE** `npm run igreen:add -- <item> [<item> ...]` — o wrapper instala E registra no `.igreen-ds/manifest.json` (baseline pro `igreen:drift`/`igreen:update`).
87
+ - **NUNCA** `npx shadcn add @igreen/...` cru: o componente instala mas fica **fora do manifesto** (o `igreen:drift` acusa "não gerenciado"). Se acontecer, recupere rodando `npm run igreen:add -- <os mesmos itens>` (idempotente; re-baseline sem sobrescrever edição).
88
+ - Commite o `.igreen-ds/manifest.json`.
@@ -0,0 +1,192 @@
1
+
2
+ # Temas de marca (iGreen DS)
3
+
4
+ O DS tem 5 marcas. Cada marca não-default é um **overlay de cor** escopado em
5
+ `[data-theme="<id>"]` que sobrescreve **só o que difere** do tema-base — 87 vars em `blue` e
6
+ `green`, 125 em `vibrant`, 166 em `pay`, contra ~350 do base.
7
+
8
+ | id | marca | arquivo |
9
+ |---|---|---|
10
+ | `default` | iGreen (verde padrão) | — **é** o tema-base, não tem overlay |
11
+ | `blue` | Azul | `brand-blue.css` |
12
+ | `green` | Verde (grass) | `brand-green.css` |
13
+ | `pay` | iGreen Pay | `brand-pay.css` |
14
+ | `vibrant` | iGreen Vibrant (verde fluorescente) | `brand-vibrant.css` |
15
+
16
+ ## ⛔ Antes de mexer: 2 fatos que causam 90% dos erros
17
+
18
+ 1. **Importar o CSS não ativa nada.** O overlay é escopado — sem
19
+ `data-theme="<id>"` no `<html>`, nenhuma regra casa e nada muda. Não existe erro:
20
+ falha em silêncio.
21
+ 2. **Ordem de import importa.** O overlay tem que vir **depois** do `tailwind-theme.css`.
22
+ Antes, o tema-base ganha por ordem de fonte.
23
+
24
+ Marca e claro/escuro são **eixos independentes**: `data-theme` no `<html>` + classe
25
+ `.dark`. Combinam livremente.
26
+
27
+ ```html
28
+ <html data-theme="vibrant" class="dark"> <!-- vibrant, escuro -->
29
+ <html data-theme="vibrant"> <!-- vibrant, claro -->
30
+ <html> <!-- default (sem atributo) -->
31
+ ```
32
+
33
+ ## Trocar o tema — identifique o modo PRIMEIRO
34
+
35
+ Existe `.claude/ds-config.json` com `"mode": "submodule"`?
36
+
37
+ ### Modo SUBMÓDULO
38
+
39
+ O **CSS** já está no disco. Importe (ajuste o caminho pro `dsPath` do `ds-config.json`):
40
+
41
+ ```css
42
+ @import "tailwindcss";
43
+ @import "../design-system/src/styles/theme/tailwind-theme.css";
44
+ @import "../design-system/src/styles/theme/brand-vibrant.css";
45
+ ```
46
+
47
+ Não rode `igreen:add` — em modo submódulo ele não se aplica. Tema novo chega com
48
+ `git pull` no submódulo. **Não** precisa de `@source`: o submódulo fica dentro da raiz do
49
+ projeto e o Tailwind v4 já escaneia daí.
50
+
51
+ ⚠️ **Duas coisas NÃO vêm com o submódulo**, e a segunda falha em silêncio:
52
+
53
+ 1. **As dependências.** O submódulo entrega código-fonte, não pacote — `npm i` das libs que os
54
+ componentes importam. O mínimo pra `Button` + `Modal`:
55
+ `tailwind-variants tailwind-merge clsx lucide-react @radix-ui/react-dialog @radix-ui/react-slot`.
56
+ O build quebra alto (`failed to resolve …`), então é fácil de achar.
57
+ 2. **Os arquivos da fonte Geist.** O `@font-face` viaja no tema, mas aponta pra `/fonts/*.woff2`
58
+ — raiz do **site**, não do submódulo. Copie:
59
+ `mkdir -p public/fonts && cp design-system/public/fonts/*.woff2 public/fonts/`.
60
+ Sem isso **não há erro**: o `font-family` segue dizendo `Geist`, o navegador recebe o
61
+ `index.html` no lugar do arquivo, e os 27 presets caem em system-ui. Confira com
62
+ `document.fonts.check("16px Geist")` — tem que ser `true`.
63
+
64
+ ### Modo COPY-IN (scaffold do CLI)
65
+
66
+ Cada tema é item do registry. Traga com o mesmo comando dos componentes:
67
+
68
+ ```bash
69
+ npm run igreen:add -- theme-vibrant # copia src/styles/theme/brand-vibrant.css
70
+ ```
71
+
72
+ Depois importe:
73
+
74
+ ```css
75
+ @import "./styles/theme/tailwind-theme.css";
76
+ @import "./styles/theme/brand-vibrant.css";
77
+ ```
78
+
79
+ ⚠️ O scaffold **apaga** os overlays não escolhidos no prompt "Tema de cor?". Se o arquivo
80
+ não está em `src/styles/theme/`, é porque outro tema foi escolhido na criação — traga com
81
+ o `igreen:add` acima.
82
+
83
+ ### Consumindo o DS por `npm install`
84
+
85
+ ⚠️ **A diretiva `@source` é OBRIGATÓRIA e é o erro nº 1 deste canal.** O Tailwind v4 **não
86
+ escaneia `node_modules`** — sem ela **nenhuma** classe do DS é gerada e os componentes
87
+ renderizam **sem estilo nenhum**, sem erro no console e sem build quebrado. Fácil concluir
88
+ que "o pacote está quebrado".
89
+
90
+ ```css
91
+ @import "tailwindcss";
92
+
93
+ /* Sem esta linha, zero classes do DS. Tem que cobrir `dist-lib/**`, não só o
94
+ index.mjs — as classes dos componentes flutuantes vivem nos *chunks*. */
95
+ @source "../node_modules/@snksergio/design-system/dist-lib/**/*.mjs";
96
+
97
+ @import "@snksergio/design-system/theme.css"; /* obrigatório */
98
+ @import "@snksergio/design-system/theme/brand-vibrant.css"; /* a marca */
99
+ ```
100
+
101
+ Ajuste o caminho do `@source` à profundidade do seu CSS de entrada (de `src/index.css`, a
102
+ raiz do projeto é `../`).
103
+
104
+ **Copie as fontes Geist** — o `@font-face` viaja no tema, mas aponta pra `/fonts/*.woff2`,
105
+ raiz do **site**:
106
+
107
+ ```bash
108
+ mkdir -p public/fonts
109
+ cp node_modules/@snksergio/design-system/dist-lib/fonts/*.woff2 public/fonts/
110
+ ```
111
+
112
+ ⚠️ Sem isso **não há erro**: o `font-family` segue dizendo `Geist`, o navegador recebe o
113
+ `index.html` no lugar do arquivo e os 27 presets caem em system-ui. Confira com
114
+ `document.fonts.check("16px Geist")` — tem que ser `true`.
115
+
116
+ Requer `@snksergio/design-system` **≥ 0.31.1** (antes disso o pacote levava só o tema-base);
117
+ as fontes só são publicadas a partir da **0.35.0**.
118
+
119
+ ## Trocar em runtime (seletor de marca)
120
+
121
+ ### Consumindo por `npm install` → use o hook `useBrand` (≥ 0.33.0)
122
+
123
+ Ele já resolve persistência, sincronia entre abas e a regra de que `default` significa
124
+ **remover** o atributo (o tema-base não tem overlay).
125
+
126
+ ```tsx
127
+ import { useBrand } from "@snksergio/design-system";
128
+
129
+ // Passe SÓ as marcas cujo overlay você importou no CSS.
130
+ const MINHAS_MARCAS = [
131
+ { id: "default", label: "iGreen", swatch: "oklch(0.5248 0.1415 150.9)" },
132
+ { id: "vibrant", label: "iGreen Vibrant", swatch: "#0fff00" },
133
+ ];
134
+
135
+ function SeletorDeMarca() {
136
+ const { brand, brands, current, setBrand } = useBrand({ brands: MINHAS_MARCAS });
137
+ return (
138
+ <select value={brand} onChange={(e) => setBrand(e.target.value)} aria-label="Marca">
139
+ {brands.map((b) => (
140
+ <option key={b.id} value={b.id}>{b.label}</option>
141
+ ))}
142
+ </select>
143
+ );
144
+ }
145
+ ```
146
+
147
+ ⚠️ **O catálogo é o ponto todo.** Sem o argumento, `useBrand` usa as 5 marcas do DS — e o
148
+ seletor listaria temas cujo CSS não está no seu bundle. `data-theme` com id sem overlay é
149
+ **no-op silencioso**: a opção aparece, o usuário clica, nada acontece, e não há erro.
150
+ Declare só o que você importou. `current` devolve a entrada ativa (label + swatch) pronta,
151
+ sem `find()`.
152
+
153
+ Valor persistido fora do catálogo cai na primeira entrada — então um `localStorage` com
154
+ `"pay"` de outro app não deixa este num tema órfão.
155
+
156
+ ### Copy-in / submódulo, ou sem o pacote npm
157
+
158
+ Escreve/remove o atributo na mão. `default` remove:
159
+
160
+ ```ts
161
+ function aplicarMarca(id: string) {
162
+ const root = document.documentElement;
163
+ if (id === "default") root.removeAttribute("data-theme");
164
+ else root.setAttribute("data-theme", id);
165
+ }
166
+ ```
167
+
168
+ Mesma armadilha: só funciona pras marcas cujo CSS **está no bundle**. Se o app oferece N
169
+ marcas ao usuário, importe os N overlays — e valide o id contra a lista que você importou,
170
+ não contra as 5 do DS.
171
+
172
+ ## Criar um tema novo
173
+
174
+ Marca muda **somente cor**. Spacing, sizing, radius, elevation e tipografia vêm sempre da
175
+ `default` — não há como uma marca alterá-los, por design. Se o pedido envolve mudar
176
+ espaçamento ou fonte "só nesta marca", **não é tema** — é outra coisa, pergunte.
177
+
178
+ Criar marca é trabalho no **repo do DS**, não aqui: 3 arquivos em `tokens/brands/<id>/`
179
+ (palette + color-light + color-dark, mesmo contrato de nomes da default) e
180
+ `npm run tokens:brand:<id>`. Neste projeto você **consome** o resultado. Se o usuário
181
+ quer uma marca que não existe, o caminho é abrir a demanda no DS — não improvisar CSS
182
+ sobrescrevendo tokens aqui (isso quebra o `protect-ds` e sai do sistema).
183
+
184
+ ## Nunca faça
185
+
186
+ - Sobrescrever CSS var de tema na unha (`--color-bg-brand: ...`) pra "simular" uma marca.
187
+ Use overlay; var solta divergem do dark, dos status e do resto do sistema.
188
+ - Duplicar o overlay dentro do projeto pra editar cor. Ele é gerado; edição some no
189
+ próximo update.
190
+ - Aplicar `data-theme` num wrapper interno em vez do `<html>`. Os dois blocos do overlay
191
+ (`[data-theme="x"]:not(.dark)` e `.dark[data-theme="x"]`) assumem marca e modo no MESMO
192
+ elemento — em elementos diferentes o light vaza pro dark.
@@ -0,0 +1,103 @@
1
+ {
2
+ "AppShell": [
3
+ "tem áreas separadas (Comercial, Financeiro…)? → `sidebar=\"menu\"` + `contexts`. Não tem? → `\"single\"` + `categories`, sem `sidebarModules` nem `sidebarShowSearch`",
4
+ "NÃO passe `sidebarLogo`: o default é a marca iGreen. Só com marca própria pedida explicitamente",
5
+ "`sidebarTitle` = nome do projeto (vai à direita da logo) — pergunte, não invente"
6
+ ],
7
+ "avatar-ig": [
8
+ "vários avatares juntos → `<AvatarGroup>`, nunca `-ml-*` + `ring` na mão: ele resolve sobreposição por tamanho, cor do anel e excedente",
9
+ "`<AvatarGroup surface=\"...\">` = a superfície ATRÁS do grupo (`table` numa linha de tabela). Errar isso põe um halo claro em volta de cada avatar",
10
+ "`max` + `total`: o `+N` conta pelo `total` (o do servidor), senão uma lista paginada mostra `+0` tendo 40 pessoas",
11
+ "`colorHex` escolhe a cor do texto por contraste WCAG (L-027) — nunca `text-white` na unha",
12
+ "foto de pessoa → `<Avatar src=\"…\">` com as iniciais em `children` (são o fallback da URL que falha), nunca `<img>` solto nem o compound do shadcn dentro do grupo"
13
+ ],
14
+ "Breadcrumb": [
15
+ "o caminho e o seletor vêm do MESMO item: `import { Breadcrumb, BreadcrumbItem, BreadcrumbSwitcher } from \"@/components/ui/Breadcrumb\"`",
16
+ "página de DETALHE (ficha de cliente, UC, contrato, chamado) → o item do registro no breadcrumb é `<BreadcrumbSwitcher>`, não texto: quem está numa ficha quer pular pra outra, não voltar à lista",
17
+ "é controlado e **não navega**: `onValueChange` devolve o `value` e quem decide rota/fetch é você",
18
+ "no `Header`, o item vira seletor com os TRÊS juntos — `switcher` + `value` + `onValueChange`; faltando um, fica texto",
19
+ "escolher valor de FORMULÁRIO não é isto: é `combobox` (o trigger dele tem cara de campo de propósito)"
20
+ ],
21
+ "CardOption": [
22
+ "switch = efeito IMEDIATO, sem Salvar, e só em `layout=\"list\"`; tela com botão Salvar → `type=\"checkbox\"`",
23
+ "opções que se COMPARAM (plano, tier, preço, descrição longa) → cards espaçados; itens do mesmo tipo com rótulo curto (settings, permissões, pagamento, endereço) → `layout=\"list\"`",
24
+ "mais de ~5 opções → não é CardOption: `Select`/`Combobox`. E on/off de UMA coisa nunca são 2 radios",
25
+ "`type=\"radio\"` EXIGE `<CardOptionGroup type=\"radio\">` em volta; checkbox e switch funcionam soltos",
26
+ "em lista o selecionado NÃO pinta por default (a borda ali é a divisória) — queira pintado? `highlightSelected` no grupo",
27
+ "omita `orientation`, `highlightSelected` e `size`: derivam do type · `md` é o calibrado"
28
+ ],
29
+ "DataTable": [
30
+ "filtro é NATIVO e reativo: `enableColumnFilter` na coluna — nunca select/form solto acima da grade. Pra ele aparecer desde o load (e não ficar escondido atrás do ícone), `showEmptyFilterChips={[\"status\", …]}`",
31
+ "não fixe `width`: com `autoFit` (default) ele é PISO e entra no rateio, não trava. Travar de verdade = `width` + `maxWidth` iguais",
32
+ "vazio são DOIS casos distintos: sem dado nenhum → `renderEmpty` (CTA de criar); filtro/busca zerou → `renderNoResults` (o \"limpar filtros\" já vem cabeado)"
33
+ ],
34
+ "FloatingPanel": [
35
+ "painel de DETALHE → siga o bloco `dsgreen-paneldetail-1`, que é o **padrão**. As variações são opt-in: use `-2` (tarefa com abas) ou `-3` (com tabela, `size=\"xl\"`) **só se o usuário citar o ID**",
36
+ "em todos: identidade ou contexto no `titleSlot`, ações de ícone `soft` + `aria-label` no `headerActions`, ação primária no `footer` — nunca botão de ação solto no corpo",
37
+ "`bodyPadded={false}` quando usar `FloatingPanelSection` (a section gerencia padding e divisória full-width)",
38
+ "é REDIMENSIONÁVEL em runtime — nenhuma largura escrita na mão acompanha o arrasto",
39
+ "aba dentro dele → `<Tabs fullWidth>` na variante default; `line` aqui vira trilho curto"
40
+ ],
41
+ "Gantt": [
42
+ "monte a tela a partir do `example-gantt` (`igreen:add example-gantt`) — é o comportamento COMPLETO, não um toy",
43
+ "o pai precisa ter ALTURA (o componente é `h-full`): sem isso você vê só a toolbar",
44
+ "busca do servidor → passe `loading`, senão `rows={[]}` afirma \"Nenhuma tarefa neste período\"",
45
+ "`colorKey` diz CATEGORIA (qual frente), não status: status vai em `row.trailing` como `Chip`"
46
+ ],
47
+ "Kpi": [
48
+ "`tone` e `KpiDelta tone` são DECISÃO, não derivação: subir nem sempre é bom (tempo de espera ↑ é ruim). `signed` só quando o sinal é literalmente bom/ruim",
49
+ "o valor sai do preset `stat-*` pela prop `size` (default `md`=24px) — nunca `text-[Npx]` na unha; fora do `Kpi`, `text-stat-* tabular-nums`",
50
+ "`divided` no `KpiGroup` vira UM card com divisórias e os filhos viram `plain` — não empilhe card dentro de card pra conseguir isso"
51
+ ],
52
+ "Modal": [
53
+ "aba dentro dele → `<Tabs fullWidth>` até o size `lg`; no `xl` (1100px) use hug",
54
+ "ação destrutiva → `AlertModal`, não confirmação montada na mão"
55
+ ],
56
+ "Panel": [
57
+ "painel de DETALHE → siga o bloco `dsgreen-paneldetail-1` (o **padrão**). `-2` (tarefa com abas) e `-3` (com tabela) só se o usuário citar o ID",
58
+ "header aqui é só STRING (`title`/`description`): precisa de avatar, Chip de status ou ação de ícone? → `FloatingPanel`",
59
+ "form dentro dele → `<FormField>` + `gap-form-gap`, nunca `<label>` cru",
60
+ "aba dentro dele → `<Tabs fullWidth>` na variante default (560px é estreito pro `line`)"
61
+ ],
62
+ "ScreenLoader": [
63
+ "omita `size`: `md` é o calibrado pro slot do AppShell; `lg` não é \"pra dar destaque\"",
64
+ "omita `skeletonLayout`: `page` serve pra qualquer tela — só mude se ela TEM KPIs no topo",
65
+ "o pai precisa ter altura, senão a variante spinner colapsa no topo",
66
+ "loading inline (botão, célula) → `Spinner` direto; overlay é composição do consumidor"
67
+ ],
68
+ "SingleMenuSidebar": [
69
+ "NÃO passe `logo`: o default é a marca iGreen. Só com marca própria pedida explicitamente",
70
+ "`title` = nome do projeto (vai à direita da logo) — pergunte, não invente",
71
+ "é a escolha quando NÃO há divisão em áreas: `showSearch={false}`, sem `module`/`modules`"
72
+ ],
73
+ "TabsNavigation": [
74
+ "aba de NAVEGADOR (sessão que abre/fecha: conversa, chamado, registro) → `<TabsNavigation>`; filtro de conteúdo dentro da tela → `tabs` do shadcn",
75
+ "`<TabsNavigation>` é SEMPRE controlado (`value` + `onValueChange`) e NÃO hospeda conteúdo — o painel pode morar fora, e aí a aba leva `panelId`",
76
+ "`surface` = a superfície do conteúdo ABAIXO da tira (`surface` num card, `canvas` na página). Errar quebra a união da aba ativa, que é o componente",
77
+ "`actions` na aba SUBSTITUI o `⋯`+`×` — é por ele que entram ✓/✗ de aceitar/recusar; sem `actions` e sem `onClose` a aba não tem ação"
78
+ ],
79
+ "tabs": [
80
+ "`variant` default (segmented) dentro de superfície (card, bloco, Panel, Modal, drawer); `line` só pra seção de página",
81
+ "`fullWidth` em superfície compacta; ⛔ nunca `w-full` no List nem `flex-1` no trigger na mão"
82
+ ],
83
+ "drawer": [
84
+ "é bottom-sheet (vaul): mobile ou gesto de arrastar. Detalhe lateral em desktop → `Panel` (bloqueia) ou `FloatingPanel` (coexiste com a lista)",
85
+ "painel de DETALHE → siga o bloco `dsgreen-paneldetail-1` (o **padrão**). `-2` (tarefa com abas) e `-3` (com tabela) só se o usuário citar o ID"
86
+ ],
87
+ "select": [
88
+ "⛔ nunca `<SelectItem value=\"\">` — é a sentinela do Radix e apaga valor em silêncio",
89
+ "em form, prefira `FormFieldSelect` (já trata a sentinela e o eco do valor)"
90
+ ],
91
+ "card": [
92
+ "`size` (`sm`/`md`/`lg`) declare SÓ no `<Card>` — Header/Content/Footer herdam por contexto; repetir nas partes é o que deixa uma seção com densidade diferente",
93
+ "`CardHeader variant=\"banded\"` com ação à direita pede `className=\"flex …\"`, NUNCA `flex-row`: o header é `grid` e `flex-row` não troca o display — o botão cai embaixo do título"
94
+ ],
95
+ "alert-dialog": [
96
+ "confirmação comum → use o composto `ui/AlertModal` (já monta tom + ícone + botões); o primitivo cru só pra caso fora do padrão",
97
+ "⚠️ ESC FECHA (só o clique fora é bloqueado). Decisão inescapável exige `onEscapeKeyDown={(e) => e.preventDefault()}`"
98
+ ],
99
+ "sonner": [
100
+ "`<Toaster/>` **1× no root**, senão o toast não aparece e não há erro",
101
+ "card ergonômico (title/description/action) → use o composto `ui/Toast`"
102
+ ]
103
+ }
@@ -0,0 +1,47 @@
1
+ > **Nota de portabilidade.** Este roteiro foi extraído do pipeline do iGreen DS
2
+ > para Claude Code. Menções a slash command (`/ds-create-…`), a `.claude/` e a
3
+ > ferramentas daquele ambiente descrevem **como ele é acionado lá** — aqui o
4
+ > equivalente é carregar este roteiro pelo `indice.json`. O conteúdo de decisão
5
+ > (entrevista, blueprint, regras de composição) vale igual.
6
+
7
+ > **Modo submódulo (`ds-link`).** Existe `.claude/ds-config.json` com `"mode": "submodule"`?
8
+ > Então **NÃO rode `igreen:add`** — esse script não existe no seu projeto. Os componentes e
9
+ > exemplos já estão no disco em `<dsPath>/src`: importe pelo `importBase` do config
10
+ > (compostos) e pelo `primitivesBase` (primitivos shadcn), e **leia** o exemplo direto de
11
+ > `<dsPath>/src/examples/`.
12
+
13
+ # Dashboard Builder (consumidor) — Blueprint + GATE
14
+
15
+ Consolide a entrevista num preview de uma tela só e **pare** — ⛔ zero edição antes do "aprovar".
16
+
17
+ ## Preview a apresentar
18
+
19
+ ```
20
+ PAINEL: <nome> · rota <definir com o usuário> · fonte: mock | API(<shape>)
21
+ Wrapper: AppShell + PageHeader | puro (chrome do print entregue? sim/não)
22
+
23
+ ⚠️ Inferido (não perguntei) — vete se discordar:
24
+ • <decisão> → <o que muda se vetar> [ou: nenhuma]
25
+
26
+ MAPA DE ROWS (top → bottom):
27
+ Row 1 — [hero/insight?] (opcional)
28
+ Row 2 — KPI-group "Painel do Líder" · N cols (§1) KPIs: <label · tom · delta(signed?)> …
29
+ Row 3 — <gráfico principal> (2/3) + <donut/resumo> (1/3) (§2)
30
+ Row 4 — <ranking / fusão KPI+evolução?> (§3)
31
+ Row 5 — <card dividido / mapa?> (§4)
32
+ Row 6 — <tabela | lista embutida?> (§5/§6 → delega crud/list-builder)
33
+
34
+ Primitivos (igreen:add): kpi · chart · panel · <data-table|data-list?> · example-dashboard (base)
35
+ ```
36
+
37
+ ## Pré-validações
38
+ - [ ] É mesmo dashboard (2+ tipos de seção)? Senão → rotear pro builder certo.
39
+ - [ ] Cada gráfico com tipo + séries; cabe no padrão (skill `charts`).
40
+ - [ ] KPI delta: `signed` só onde sinal = bom/ruim; senão tom explícito.
41
+ - [ ] Ícone: KPI-group = círculo; mini-stat/legenda = quadrado.
42
+ - [ ] Tabela/lista embutida delega a crud/list-builder; distribuição correta.
43
+ - [ ] Layout estreito não coloca cards lado-a-lado apertados; zero hardcode.
44
+
45
+ ## Gate
46
+ Apresentar mapa de rows + pré-validações numa tela só. Aguardar `aprovar` → `generate.md`;
47
+ `ajustar X` → re-montar; `cancelar` → abortar (zero edits). **Não tocar em arquivo antes do aprovar.**
@@ -0,0 +1,62 @@
1
+ > **Nota de portabilidade.** Este roteiro foi extraído do pipeline do iGreen DS
2
+ > para Claude Code. Menções a slash command (`/ds-create-…`), a `.claude/` e a
3
+ > ferramentas daquele ambiente descrevem **como ele é acionado lá** — aqui o
4
+ > equivalente é carregar este roteiro pelo `indice.json`. O conteúdo de decisão
5
+ > (entrevista, blueprint, regras de composição) vale igual.
6
+
7
+ > **Modo submódulo (`ds-link`).** Existe `.claude/ds-config.json` com `"mode": "submodule"`?
8
+ > Então **NÃO rode `igreen:add`** — esse script não existe no seu projeto. Os componentes e
9
+ > exemplos já estão no disco em `<dsPath>/src`: importe pelo `importBase` do config
10
+ > (compostos) e pelo `primitivesBase` (primitivos shadcn), e **leia** o exemplo direto de
11
+ > `<dsPath>/src/examples/`.
12
+
13
+ # Dashboard Builder (consumidor) — Entrevista (fases 0–6)
14
+
15
+ Uma pergunta (ou grupo curto) por vez. Acumule as escolhas — **ZERO edição em disco**
16
+ até o gate. Use `AskUserQuestion` pra escolhas fechadas; proponha um default sensato.
17
+ As receitas de cada seção estão no `generate.md` (§1–§6) + no `example-dashboard`.
18
+
19
+ ## Fase 0 — Intent + fonte
20
+ - Do que é o painel? (visão geral de quê, pra quem — ex.: painel do líder, resumo de
21
+ categoria, financeiro consolidado).
22
+ - Dados: mock ou API? Se API, anote o shape → a geração cria mock tipado espelhando.
23
+ - Período/segmentação no topo? (quase sempre → PeriodSelector no `actions` do PageHeader).
24
+
25
+ - **Envelope (wrapper)** — verifique antes de perguntar: existe `AppShell`? A referência
26
+ mostra rail/menu/header? Chrome sem shell → ofereça `/ds-create-app` junto; recusado →
27
+ registre no blueprint o que do print fica de fora.
28
+
29
+ ## Fase 1 — KPIs rápidos ("Painel do Líder", §1)
30
+ - Quais métricas rápidas? (label + valor + tom do ícone).
31
+ - Delta? O sinal é literalmente bom/ruim → `signed`. Senão, tom explícito (tempo ↓ = success).
32
+ - Quantas colunas (4–6)? → `<KpiGroup columns={N} divided>`.
33
+
34
+ ## Fase 2 — Gráficos (chart-cards, §2)
35
+ - Quais gráficos? Tipo (barras/linha/área/pizza-donut), séries, o que comparam (ver skill `charts`).
36
+ - Cada um num SectionCard (head título+subtítulo discreto). Big-number liderando? Legenda/metric-rows?
37
+ - Donut com total no centro? 2 séries → verde+âmbar; pizza → rampa da marca.
38
+
39
+ ## Fase 3 — Ranking / fusão KPI+evolução (§3)
40
+ - Ranking (reconhecimento, top do mês) ou linha de lista rica que funde identidade +
41
+ mini-KPIs em colunas + métrica headline com delta (ex.: cidades/licenciados por status)?
42
+ - Se sim: mini-KPIs (label+ícone+tom), métrica headline à direita (delta Chip), identificador (rank/nome/sub).
43
+
44
+ ## Fase 4 — Card dividido em 2 / mapa (§4)
45
+ - Seção que junta dados + visual lado a lado (ex.: barras por UF | mapa)? → card dividido (divisor no 2º painel).
46
+ - Precisa de mapa? (SVG por região/UF + legenda). Anote regiões/UFs.
47
+
48
+ ## Fase 5 — Tabela / lista embutida (delega)
49
+ - Termina com tabela (grade) ou lista (cards) de detalhe?
50
+ - Tabela → geração delega a distribuição de colunas ao `crud-builder` (identidade → status →
51
+ categóricos → muted → moeda/data à direita; filtros nativos pré-aplicados).
52
+ - Lista/kanban → delega ao `list-builder` (linha1 título+secundário, linha2 meta, status chip,
53
+ valor à direita; sem botão de ação).
54
+ - Nenhuma → pula.
55
+
56
+ ## Fase 6 — Layout das rows
57
+ - Ordem/agrupamento. Padrão: (1) hero/insight opcional · (2) KPI-group "Painel do Líder" faixa larga ·
58
+ (3) gráfico principal (2/3) + donut/resumo (1/3) · (4) qualidade/ranking/fusão · (5) listas/tabela.
59
+ - Estreito → 1 card/row + coluna única. Rows 2–3 col via `grid grid-cols-1 lg:grid-cols-{2,3} gap-gp-2xl items-stretch`.
60
+
61
+ ## Fim
62
+ Resumir em 1 frase + `BLUEPRINT_PRONTO: <Painel> (dashboard) — aguardando gate` → carregar `blueprint.md`.