ganbatte-os 0.2.37 → 0.2.41

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 (38) hide show
  1. package/.gos/agents/profiles/ganbatte-os-master.md +100 -0
  2. package/.gos/libraries/caveman-rules.md +58 -0
  3. package/.gos/libraries/cloudflare-stack-kb.md +161 -0
  4. package/.gos/libraries/component-reuse-gate.md +75 -0
  5. package/.gos/libraries/default-stack-kb.md +98 -0
  6. package/.gos/libraries/engineering-best-practices.md +208 -0
  7. package/.gos/libraries/gos-compress-setup.md +62 -0
  8. package/.gos/libraries/intake-questions-mom-test.md +91 -0
  9. package/.gos/libraries/lucide-icons-policy.md +174 -0
  10. package/.gos/libraries/security-best-practices.md +138 -0
  11. package/.gos/libraries/supabase-stack-kb.md +124 -0
  12. package/.gos/libraries/timer-pattern-spec.md +252 -0
  13. package/.gos/libraries/typeform-pattern-spec.md +204 -0
  14. package/.gos/libraries/ui-guardrails-checklist.md +144 -0
  15. package/.gos/libraries/visual-diff-lenses.md +114 -0
  16. package/.gos/playbooks/audit-streaming-playbook.md +86 -0
  17. package/.gos/skills/adr-tech-decisions/SKILL.md +166 -0
  18. package/.gos/skills/audit-screenshots/SKILL.md +200 -142
  19. package/.gos/skills/cloudflare-pages-setup/SKILL.md +180 -0
  20. package/.gos/skills/figma-print-diff/SKILL.md +170 -0
  21. package/.gos/skills/gos-caveman/SKILL.md +110 -0
  22. package/.gos/skills/gos-compress/SKILL.md +134 -0
  23. package/.gos/skills/gos-compress/scripts/compress.py +346 -0
  24. package/.gos/skills/gos-compress/scripts/setup.py +91 -0
  25. package/.gos/skills/idea-intake/SKILL.md +147 -0
  26. package/.gos/skills/plan-blueprint/SKILL.md +18 -3
  27. package/.gos/skills/plan-to-tasks/SKILL.md +37 -1
  28. package/.gos/skills/prd-from-intake/SKILL.md +94 -0
  29. package/.gos/skills/prototype-orchestrator/SKILL.md +120 -0
  30. package/.gos/skills/registry.json +12 -1
  31. package/.gos/skills/timer-component-pattern/SKILL.md +245 -0
  32. package/.gos/skills/typeform-form-pattern/SKILL.md +210 -0
  33. package/.gos/skills/ui-guardrails/SKILL.md +111 -0
  34. package/.gos/templates/intakeTemplate.md +41 -0
  35. package/.gos/templates/planTemplate.md +25 -4
  36. package/.gos/templates/prdLeanTemplate.md +40 -0
  37. package/.gos/templates/taskTemplate.md +29 -5
  38. package/package.json +1 -1
@@ -0,0 +1,124 @@
1
+ # Supabase Stack KB — Free Tier (2026-05)
2
+
3
+ > Referencia consultada por `adr-tech-decisions`. Data: 2026-05.
4
+ > Sempre verificar https://supabase.com/pricing para limites atuais.
5
+
6
+ ## Filosofia G-OS para Supabase
7
+
8
+ 1. Usar quando precisa Postgres real, auth pronta, ou Realtime escalavel.
9
+ 2. Combina bem com Cloudflare Pages (front) + Supabase (backend completo).
10
+ 3. Free tier projetos pausam apos 7 dias inativos — atencao para MVPs descartaveis em demo.
11
+ 4. **Anti-vendor-lock**: Postgres standard, Auth pode ser substituida (Auth.js, Lucia).
12
+
13
+ ## Free tier (2 projetos ativos simultaneos)
14
+
15
+ ### Database (Postgres)
16
+ - 500MB storage
17
+ - 2GB bandwidth/mes
18
+ - 50k MAU em Auth
19
+ - 5GB storage de arquivos (Storage)
20
+ - 7 dias de inatividade -> projeto pausa (precisa logar e dar resume)
21
+
22
+ ### Auth (free 50k MAU)
23
+ - Email/password, magic link, OAuth (Google, GitHub, ~20 providers)
24
+ - Row Level Security (RLS) Postgres-based
25
+ - JWT signing automatico
26
+ - **Limite: 4 social providers em free, 30 em pro**
27
+
28
+ ### Realtime (free)
29
+ - Postgres logical replication -> canais por tabela
30
+ - 200 conexoes simultaneas
31
+ - 2M mensagens/mes
32
+ - **Limite: 200 conexoes pode ser baixo para chat publico; suficiente para dashboard interno**
33
+
34
+ ### Storage (free)
35
+ - 5GB
36
+ - 5GB egress/mes
37
+ - Image transformations (resize, format)
38
+
39
+ ### Edge Functions (free)
40
+ - 500k invocations/mes
41
+ - 2s execution time
42
+ - Deno-based
43
+ - Bom para webhooks, cron jobs, API routes leves
44
+
45
+ ## Patterns recomendados
46
+
47
+ ### Pattern: Front Cloudflare + Back Supabase (recomendado MVP rapido)
48
+
49
+ ```
50
+ [Cloudflare Pages] [Supabase]
51
+ Vite + React Postgres + Auth + Storage
52
+ | |
53
+ +--- supabase-js client ---+
54
+ (direto do browser, RLS protege)
55
+ ```
56
+
57
+ Pros:
58
+ - Sem Workers necessarios
59
+ - Auth pronta em 1 hora
60
+ - RLS substitui boa parte de backend
61
+
62
+ Contras:
63
+ - Vendor lock no Auth (mas Postgres e portavel)
64
+ - Cold start de project apos 7 dias de inatividade
65
+
66
+ ### Pattern: Cloudflare Workers + Supabase Postgres (apenas DB)
67
+
68
+ Use quando:
69
+ - Quer logica de servidor (Workers) mas precisa Postgres real (D1 nao basta).
70
+ - Auth simples ou via Cloudflare Access.
71
+
72
+ ## Realtime: Supabase Realtime vs Cloudflare DO+WS
73
+
74
+ | Criterio | Supabase Realtime | Cloudflare DO+WS |
75
+ |----------|-------------------|------------------|
76
+ | Custo MVP | Free 200 conn | Free 1M ops/mes |
77
+ | Modelo | Postgres-based (subscribe a INSERT/UPDATE) | Codigo livre (qualquer mensagem) |
78
+ | Boa para | Notificacoes vindas de DB | Chat, jogo, presenca |
79
+ | Limite escala | 200 conn free / 500 pro | 32k por DO, infinito DOs |
80
+ | Latencia | ~100ms | ~50ms (edge) |
81
+ | Setup | `.from('table').on('INSERT', ...)` | Implementar `webSocketMessage()` |
82
+
83
+ **Regra G-OS**: usuario indicou "chat ao vivo" sem DB transacional -> DO+WS. Indicou "notificacao quando alguem cadastra X" -> Supabase Realtime.
84
+
85
+ ## Anti-patterns
86
+
87
+ - Free project como demo permanente: pausa em 7 dias, demo quebra.
88
+ - RLS desabilitado em prod: vazamento de dados.
89
+ - service_role key exposta no front: bypassa RLS — NUNCA por no Pages.
90
+ - Storage publico sem moderacao: ataques de upload em MVP.
91
+
92
+ ## Bindings (Cloudflare Workers consumindo Supabase)
93
+
94
+ ```typescript
95
+ import { createClient } from '@supabase/supabase-js'
96
+
97
+ export default {
98
+ async fetch(req: Request, env: Env) {
99
+ const supabase = createClient(
100
+ env.SUPABASE_URL,
101
+ env.SUPABASE_ANON_KEY // ou SERVICE_ROLE pra ops privilegiadas
102
+ )
103
+ // ...
104
+ }
105
+ }
106
+ ```
107
+
108
+ `wrangler secret put SUPABASE_URL` e `SUPABASE_ANON_KEY`.
109
+
110
+ ## Limites para alarmar
111
+
112
+ | Recurso | Limite free | Alarme |
113
+ |---------|-------------|--------|
114
+ | DB storage | 500MB | >300MB |
115
+ | MAU auth | 50k | >30k |
116
+ | Realtime conn | 200 | >100 sustentadas |
117
+ | Edge Function invocations | 500k/mes | >300k/mes |
118
+ | Inatividade | 7 dias | demos publicas precisam ping |
119
+
120
+ ## Ferramentas
121
+
122
+ - **CLI**: `npm i -g supabase` — `supabase init`, `supabase db push`, migrations
123
+ - **Studio**: dashboard web em supabase.com — SQL editor, table editor, Auth users
124
+ - **Local dev**: `supabase start` (precisa Docker) — sobe Postgres + Auth + Storage local
@@ -0,0 +1,252 @@
1
+ # Timer Pattern — Spec abstrata
2
+
3
+ > Abstracao do timer-de-estudo passo-a-passo, baseada em `code-kata-timer/script.js`
4
+ > + `code-kata-timer/style.css`. Adaptado para React + TypeScript + Tailwind.
5
+ >
6
+ > Use quando o usuario pedir contador regressivo, timer de pomodoro, code kata,
7
+ > stopwatch com countdown, ou qualquer interface de tempo com play/pause/edit.
8
+
9
+ ## State machine
10
+
11
+ ```ts
12
+ export const TimerStatus = {
13
+ STOPPED: 'STOPPED',
14
+ COUNTDOWN: 'COUNTDOWN', // animacao 3-2-1-GO antes de iniciar
15
+ RUNNING: 'RUNNING',
16
+ PAUSED: 'PAUSED',
17
+ EDITING: 'EDITING', // usuario editando valor
18
+ } as const;
19
+ export type TimerStatusType = typeof TimerStatus[keyof typeof TimerStatus];
20
+
21
+ export const isRunning = (s: TimerStatusType) =>
22
+ s === TimerStatus.RUNNING || s === TimerStatus.COUNTDOWN;
23
+ ```
24
+
25
+ Transicoes validas:
26
+ ```
27
+ STOPPED -> COUNTDOWN -> RUNNING -> PAUSED -> RUNNING
28
+ -> STOPPED
29
+ EDITING <-> STOPPED (so edita parado)
30
+ ```
31
+
32
+ ## Persistencia
33
+
34
+ LocalStorage com chave `timerSettings`:
35
+ ```ts
36
+ interface TimerSettings {
37
+ currentSeconds: number;
38
+ timerStatus: TimerStatusType; // sempre STOPPED ao recarregar
39
+ theme: 'dark' | 'light';
40
+ showContributors?: boolean; // app-specific, opcional
41
+ }
42
+ ```
43
+
44
+ **Regra critica** (do code-kata-timer original): ao carregar settings, **sempre forcar status para STOPPED** — nao restaurar RUNNING. Caso contrario timer "fantasma" continua contando.
45
+
46
+ ## Hook reutilizavel
47
+
48
+ ```ts
49
+ export function useTimer(defaultSeconds = 30) {
50
+ const [status, setStatus] = useState<TimerStatusType>(TimerStatus.STOPPED);
51
+ const [seconds, setSeconds] = useState(defaultSeconds);
52
+ const intervalRef = useRef<number | null>(null);
53
+
54
+ const start = useCallback(() => {
55
+ if (status === TimerStatus.RUNNING) return;
56
+ setStatus(TimerStatus.RUNNING);
57
+ intervalRef.current = window.setInterval(() => {
58
+ setSeconds(s => {
59
+ if (s <= 1) {
60
+ if (intervalRef.current) clearInterval(intervalRef.current);
61
+ setStatus(TimerStatus.STOPPED);
62
+ // dispatch event 'timer:done' para componentes ouvirem
63
+ window.dispatchEvent(new CustomEvent('timer:done'));
64
+ return 0;
65
+ }
66
+ return s - 1;
67
+ });
68
+ }, 1000);
69
+ }, [status]);
70
+
71
+ const pause = useCallback(() => {
72
+ if (intervalRef.current) clearInterval(intervalRef.current);
73
+ setStatus(TimerStatus.PAUSED);
74
+ }, []);
75
+
76
+ const reset = useCallback((to = defaultSeconds) => {
77
+ if (intervalRef.current) clearInterval(intervalRef.current);
78
+ setSeconds(to);
79
+ setStatus(TimerStatus.STOPPED);
80
+ }, [defaultSeconds]);
81
+
82
+ useEffect(() => {
83
+ return () => {
84
+ if (intervalRef.current) clearInterval(intervalRef.current);
85
+ };
86
+ }, []);
87
+
88
+ return { status, seconds, start, pause, reset, setSeconds };
89
+ }
90
+ ```
91
+
92
+ ## Display
93
+
94
+ Formato `MM:SS` com font monospace (Orbitron, JetBrains Mono ou Tailwind `font-mono`):
95
+
96
+ ```tsx
97
+ function formatTime(s: number) {
98
+ const m = Math.floor(s / 60);
99
+ const ss = s % 60;
100
+ return `${String(m).padStart(2, '0')}:${String(ss).padStart(2, '0')}`;
101
+ }
102
+
103
+ <div className="text-7xl md:text-9xl font-mono font-bold tabular-nums">
104
+ {formatTime(seconds)}
105
+ </div>
106
+ ```
107
+
108
+ `tabular-nums` garante que digitos nao "pulem" durante a contagem.
109
+
110
+ ## Controles (Lucide React)
111
+
112
+ ```tsx
113
+ import { Play, Pause, RotateCcw, Pencil } from "lucide-react";
114
+
115
+ <div className="flex gap-3 justify-center">
116
+ {status === TimerStatus.STOPPED || status === TimerStatus.PAUSED ? (
117
+ <Button onClick={start} size="lg" aria-label="Iniciar">
118
+ <Play className="h-5 w-5" />
119
+ </Button>
120
+ ) : (
121
+ <Button onClick={pause} size="lg" aria-label="Pausar">
122
+ <Pause className="h-5 w-5" />
123
+ </Button>
124
+ )}
125
+ <Button onClick={() => reset()} size="lg" variant="outline" aria-label="Resetar">
126
+ <RotateCcw className="h-5 w-5" />
127
+ </Button>
128
+ <Button
129
+ onClick={() => setStatus(TimerStatus.EDITING)}
130
+ size="lg"
131
+ variant="outline"
132
+ aria-label="Editar tempo"
133
+ disabled={isRunning(status)}
134
+ >
135
+ <Pencil className="h-5 w-5" />
136
+ </Button>
137
+ </div>
138
+ ```
139
+
140
+ ## Atalhos de teclado
141
+
142
+ ```ts
143
+ useEffect(() => {
144
+ function onKey(e: KeyboardEvent) {
145
+ if (e.target instanceof HTMLInputElement) return; // nao interferir em editing
146
+ if (e.code === 'Space') { e.preventDefault(); status === TimerStatus.RUNNING ? pause() : start(); }
147
+ if (e.code === 'KeyR') reset();
148
+ if (e.code === 'KeyE') setStatus(TimerStatus.EDITING);
149
+ }
150
+ window.addEventListener('keydown', onKey);
151
+ return () => window.removeEventListener('keydown', onKey);
152
+ }, [status, start, pause, reset]);
153
+ ```
154
+
155
+ ## Tema
156
+
157
+ Variaveis CSS (root data-theme):
158
+ - `--timer-bg`: bg principal
159
+ - `--timer-fg`: cor do texto/digitos
160
+ - `--timer-accent`: cor do CTA primario
161
+
162
+ Toggle via `document.documentElement.dataset.theme = 'dark' | 'light'`. Persistir em `localStorage`.
163
+
164
+ ## Edicao inline
165
+
166
+ Ao entrar em `EDITING`:
167
+ - Display vira `<input type="number">` com mesmo styling do display.
168
+ - Enter -> salva e volta para `STOPPED`.
169
+ - Esc -> cancela e volta para `STOPPED`.
170
+
171
+ ```tsx
172
+ {status === TimerStatus.EDITING ? (
173
+ <input
174
+ type="number"
175
+ autoFocus
176
+ defaultValue={seconds}
177
+ className="text-7xl font-mono font-bold tabular-nums bg-transparent w-64 text-center"
178
+ onBlur={(e) => {
179
+ const v = Number(e.currentTarget.value);
180
+ if (v > 0) setSeconds(v);
181
+ setStatus(TimerStatus.STOPPED);
182
+ }}
183
+ onKeyDown={(e) => {
184
+ if (e.key === 'Enter') e.currentTarget.blur();
185
+ if (e.key === 'Escape') setStatus(TimerStatus.STOPPED);
186
+ }}
187
+ />
188
+ ) : (
189
+ <div>{formatTime(seconds)}</div>
190
+ )}
191
+ ```
192
+
193
+ ## Eventos customizados
194
+
195
+ Dispatch quando timer termina:
196
+ ```ts
197
+ window.dispatchEvent(new CustomEvent('timer:done', { detail: { duration } }));
198
+ ```
199
+
200
+ Componentes ouvem:
201
+ ```ts
202
+ useEffect(() => {
203
+ const handler = (e: Event) => {
204
+ const evt = e as CustomEvent<{ duration: number }>;
205
+ // mostrar toast, tocar som, etc
206
+ };
207
+ window.addEventListener('timer:done', handler);
208
+ return () => window.removeEventListener('timer:done', handler);
209
+ }, []);
210
+ ```
211
+
212
+ ## Som ao terminar (opcional)
213
+
214
+ Web Audio API simples — sem precisar arquivo:
215
+ ```ts
216
+ function beep(frequency = 800, duration = 200) {
217
+ const ctx = new AudioContext();
218
+ const osc = ctx.createOscillator();
219
+ const gain = ctx.createGain();
220
+ osc.connect(gain);
221
+ gain.connect(ctx.destination);
222
+ osc.frequency.value = frequency;
223
+ osc.start();
224
+ setTimeout(() => { osc.stop(); ctx.close(); }, duration);
225
+ }
226
+ ```
227
+
228
+ ## Estados visuais (UI guardrails)
229
+
230
+ - **Loading**: nao aplicavel (timer e estado puro).
231
+ - **Empty**: tempo zerado mostra `00:00` mas botao Iniciar disabled.
232
+ - **Error**: nao deve haver erro em runtime — se houver, fallback para reset.
233
+ - **Running indicator**: Lucide `Activity` pulsando ou borda do display animada.
234
+
235
+ ## Anti-patterns
236
+
237
+ - `setInterval` sem cleanup -> memory leak.
238
+ - Restaurar status RUNNING do localStorage -> timer fantasma.
239
+ - Sem `tabular-nums` -> digitos pulam visualmente.
240
+ - Sem atalho de teclado -> mau UX em uso intensivo.
241
+ - Emoji nos botoes -> usar Lucide.
242
+ - Sem dispatch de evento ao terminar -> componentes externos nao reagem.
243
+
244
+ ## Reuso no G-OS
245
+
246
+ `gos-master` reconhece pedidos:
247
+ - "timer", "contador regressivo", "countdown"
248
+ - "pomodoro", "pomodoro timer"
249
+ - "code kata timer", "kata"
250
+ - "stopwatch" (variacao — countup vs countdown)
251
+
252
+ Aciona skill `timer-component-pattern` que aplica spec acima.
@@ -0,0 +1,204 @@
1
+ # Typeform Pattern — Spec abstrata
2
+
3
+ > Abstracao do quiz/form passo-a-passo estilo Typeform, baseada em
4
+ > `body-metrics-edge/.examples/tsx/old/body-metrics-quiz-typeform.tsx`
5
+ > e `template-lp-wanderson/.a8z/skills/designkit/templates/form.tsx.template`.
6
+ >
7
+ > Use sempre que o usuario pedir form de coleta de dados, quiz, onboarding multi-step,
8
+ > survey, lead form, ou qualquer entrada estruturada que beneficia de **uma pergunta por vez**.
9
+
10
+ ## Por que esse pattern
11
+
12
+ - **Conversao 2-3x maior** que forms longos (uma tela por pergunta vs paredao de inputs).
13
+ - **Cognitive load baixo** — usuario ve so uma decisao por vez.
14
+ - **Mobile-first natural** — funciona perfeitamente em telas pequenas.
15
+ - **Estado intermediario salvavel** — refresh nao perde progresso.
16
+
17
+ ## Anatomia do pattern
18
+
19
+ ### 1. Question schema
20
+
21
+ ```ts
22
+ export type QuestionType = 'welcome' | 'text' | 'number' | 'choice' | 'multiChoice' | 'date' | 'review';
23
+
24
+ export interface QuestionOption {
25
+ value: string;
26
+ label: string;
27
+ description?: string;
28
+ iconName?: keyof typeof import('lucide-react'); // Lucide React (zero emoji)
29
+ }
30
+
31
+ export interface Question<F = Record<string, unknown>> {
32
+ id: string;
33
+ type: QuestionType;
34
+ field: keyof F | null;
35
+ question: string | ((data: F) => string); // pode ser dinamica
36
+ description?: string;
37
+ placeholder?: string;
38
+ suffix?: string;
39
+ decimal?: boolean;
40
+ buttonText?: string;
41
+ options?: QuestionOption[];
42
+ validate?: (value: unknown, data: F) => string | null; // null = ok, string = erro
43
+ showWhen?: (data: F) => boolean; // condicional
44
+ }
45
+ ```
46
+
47
+ ### 2. State
48
+
49
+ ```ts
50
+ const [currentIndex, setCurrentIndex] = useState(0);
51
+ const [data, setData] = useState<FormData>({});
52
+ const [isAnimating, setIsAnimating] = useState(false);
53
+ ```
54
+
55
+ Persistir em `sessionStorage` ou `Supabase row` se descartavel = false.
56
+
57
+ ### 3. Filtro de ativas (condicional)
58
+
59
+ ```ts
60
+ const activeQuestions = questions.filter(q => !q.showWhen || q.showWhen(data));
61
+ const currentQ = activeQuestions[currentIndex];
62
+ ```
63
+
64
+ ### 4. Navegacao
65
+
66
+ - `Enter` -> next (com validacao).
67
+ - `Shift+Enter` (em textarea) -> nova linha.
68
+ - `Esc` -> previous (opcional).
69
+ - Botao "Voltar" sempre visivel exceto na welcome.
70
+ - Animacao opacity+translate-y com 200-300ms entre transicoes.
71
+
72
+ ### 5. Componentes-tipo
73
+
74
+ **Welcome screen**
75
+ ```
76
+ - Titulo grande (text-4xl/5xl)
77
+ - Subtitulo
78
+ - CTA primario "Comecar" (Button size lg)
79
+ - Lucide icon ilustrativo (h-16 w-16)
80
+ ```
81
+
82
+ **Text input**
83
+ ```
84
+ - Question (text-2xl semibold)
85
+ - Description opcional (text-base muted-foreground)
86
+ - Input grande (h-12 ou h-14, text-lg)
87
+ - Hint "Pressione Enter para continuar" + CornerDownLeft icon
88
+ ```
89
+
90
+ **Number input**
91
+ ```
92
+ - Igual text input mas type="number"
93
+ - Suffix visual ao lado (ex: "anos", "kg", "cm")
94
+ - Decimal opt-in via prop
95
+ ```
96
+
97
+ **Choice (radio)**
98
+ ```
99
+ - Stack vertical de cards clicaveis
100
+ - Cada card: Lucide icon + label + description opcional
101
+ - Hover ring + active filled
102
+ - Selecao avanca automaticamente apos 300ms
103
+ ```
104
+
105
+ **MultiChoice (checkbox)**
106
+ ```
107
+ - Igual choice mas selecao acumula
108
+ - Botao "Continuar" obrigatorio (nao auto-avanca)
109
+ - Mostra contagem ("3 de 5 selecionados")
110
+ ```
111
+
112
+ **Review (final)**
113
+ ```
114
+ - Resumo dos dados coletados em cards
115
+ - Botao "Editar" por secao (volta para a question correspondente)
116
+ - CTA primario "Confirmar" / "Calcular" / "Enviar"
117
+ ```
118
+
119
+ ### 6. Animacao
120
+
121
+ ```tsx
122
+ <div className={`transition-all duration-300 ${
123
+ isAnimating ? 'opacity-0 translate-y-4' : 'opacity-100 translate-y-0'
124
+ }`}>
125
+ {/* question */}
126
+ </div>
127
+ ```
128
+
129
+ ### 7. Progress bar
130
+
131
+ Top of screen, fixed:
132
+ ```tsx
133
+ <div className="fixed top-0 left-0 right-0 h-1 bg-muted">
134
+ <div
135
+ className="h-full bg-primary transition-all duration-300"
136
+ style={{ width: `${((currentIndex + 1) / activeQuestions.length) * 100}%` }}
137
+ />
138
+ </div>
139
+ ```
140
+
141
+ ### 8. Validacao
142
+
143
+ Por question:
144
+ ```ts
145
+ {
146
+ id: 'email',
147
+ type: 'text',
148
+ field: 'email',
149
+ question: 'Qual seu email?',
150
+ validate: (value) => {
151
+ if (!value) return 'Campo obrigatorio';
152
+ if (!/^[^@]+@[^@]+\.[^@]+$/.test(String(value))) return 'Email invalido';
153
+ return null;
154
+ }
155
+ }
156
+ ```
157
+
158
+ Erro inline abaixo do input. Nunca toast/alert.
159
+
160
+ ### 9. Estados visuais (UI guardrails)
161
+
162
+ - **Loading** (submitting): botao disabled + Lucide `Loader2` com `animate-spin`.
163
+ - **Empty** (review sem dados): mostrar "Voce ainda nao respondeu — comece pelo inicio."
164
+ - **Error** (submit falhou): inline acima do CTA + botao retry.
165
+ - **Success**: tela final com Lucide `CheckCircle2` h-16 w-16 + redirect ou prox-step.
166
+
167
+ ### 10. Acessibilidade
168
+
169
+ - `aria-live="polite"` no container de question (anuncia mudanca pra screen reader).
170
+ - Focus auto no input ao mudar de question (`useEffect` + `inputRef.current?.focus()`).
171
+ - Labels via `<Label htmlFor>` ou `aria-label`.
172
+ - Botao voltar com `aria-label="Pergunta anterior"`.
173
+
174
+ ## Stack obrigatorio
175
+
176
+ - React 18+
177
+ - TypeScript strict
178
+ - Tailwind v4 + shadcn/ui (Button, Input, Card, Label)
179
+ - Lucide React (UNICA lib de icones — zero emoji)
180
+ - React Hook Form + Zod (validacao schema-first quando form complexo)
181
+
182
+ ## Variantes
183
+
184
+ - **Quiz com calculo** (body-metrics-quiz original): apos ultima question, calcula metricas e mostra resultado.
185
+ - **Lead form** (capturar email/whats): submita para Supabase.
186
+ - **Onboarding** (apos signup): salva preferencias na profiles table.
187
+ - **Survey publico** (NPS, feedback): tabela com RLS public-insert apenas.
188
+
189
+ ## Anti-patterns
190
+
191
+ - Mostrar todas perguntas de uma vez como form tradicional — defeats the purpose.
192
+ - Sem progress bar — usuario nao sabe quanto falta.
193
+ - Pular validacao no front — mau UX (back so pra forma).
194
+ - Auto-avanco SEM debounce em choice — usuario clica errado e ja avancou.
195
+ - Texto unicode/emoji nas opcoes — usar `iconName` Lucide.
196
+ - Sem estado de submitting no final — usuario clica enviar 3x.
197
+
198
+ ## Reuso no G-OS
199
+
200
+ Quando usuario pede "form de cadastro", "quiz", "onboarding", "captura de leads":
201
+ 1. `gos-master` sugere pattern typeform.
202
+ 2. Usuario aprova -> skill `typeform-form-pattern` gera estrutura base.
203
+ 3. Customiza questions[] do dominio.
204
+ 4. Estilizacao via tokens shadcn (sem hardcoded colors).