397 lines
12 KiB
Markdown
397 lines
12 KiB
Markdown
# Learnings — Padrões bem-sucedidos
|
|
|
|
_Registro de padrões que funcionam, para replicar._
|
|
|
|
---
|
|
|
|
## [LRN-20260520-003] gitea-api-autenticacao-completa
|
|
|
|
**Logged**: 2026-05-20T12:31:00-03:00
|
|
**Priority**: high
|
|
**Status**: reference
|
|
**Area**: devops | api
|
|
|
|
### Summary
|
|
Gitea API suporta 5 métodos de autenticação — todos usam o mesmo API token
|
|
|
|
### Details
|
|
Fontes estudadas: https://docs.gitea.com/development/api-usage
|
|
|
|
**Métodos de autenticação (ordem de preferência):**
|
|
1. `Authorization: token <hash>` — mais comum, não usa username na URL
|
|
2. `token=<hash>` query string — simples para curl/GETs
|
|
3. `access_token=<hash>` query string — compat OAuth2 consumers
|
|
4. HTTP Basic (`username:password`) — cria token, admin
|
|
5. HTTP Basic + OTP — quando 2FA habilitado (`X-Gitea-OTP: 123456`)
|
|
6. SSH HTTP Signature — chaves SSH registradas, draft-cavage-http-signatures
|
|
|
|
**Token nunca é reexibido** — apare | |:"`name":"meu-token","sha1":"9fcb11..."` — só sha1 na listagem /**/
|
|
**Scopes**: formato `<read|write>:<permission>`, ou `all` para tudo.
|
|
Permissions: activitypub, admin, issue, misc, notification, organization, package, repository, user
|
|
|
|
**Pagination**: `?page=N&limit=N`, header `Link` (`rel=next/last`), `x-total-count`
|
|
|
|
**Sudo (admin)**: adicionar `Sudo: username` header ou param `sudo=username`
|
|
|
|
### Suggested Action
|
|
Usar sempre `scopes:["all"]` para automação, guardar token em segredo,
|
|
preferir header `Authorization: token` sobre query string.
|
|
|
|
### Metadata
|
|
- Source: documentation
|
|
- Tags: gitea, api, auth, token, swagger, devops
|
|
- Pattern-Key: gitea-api.authentication
|
|
- Recurrence-Count: 1
|
|
|
|
---
|
|
|
|
## [LRN-20260520-004] gitea-api-endpoints-reference
|
|
|
|
**Logged**: 2026-05-20T12:31:00-03:00
|
|
**Priority**: medium
|
|
**Status**: reference
|
|
**Area**: devops | api
|
|
|
|
### Summary
|
|
Mapa dos endpoints Gitea API v1 por domínio
|
|
|
|
### Details
|
|
- **Usuário**: `GET /api/v1/user`, `GET /api/v1/users/:username`
|
|
- **Repos**: `GET/POST /api/v1/user/repos`, `GET /api/v1/repos/:owner/:repo`
|
|
- **Issues**: `GET/POST /api/v1/repos/:owner/:repo/issues`
|
|
- **Pulls**: `GET/POST /api/v1/repos/:owner/:repo/pulls`
|
|
- **Webhooks**: `GET/POST /api/v1/repos/:owner/:repo/hooks`
|
|
- **Admin users**: `GET/POST/PUT/DELETE /api/v1/admin/users`
|
|
- **Swagger**: `https://host/api/swagger`
|
|
- **OpenAPI JSON**: `https://host/swagger.v1.json`
|
|
|
|
### Suggested Action
|
|
Consultar SKILL.md `skills/gitea-api/SKILL.md` antes de usar a API.
|
|
|
|
### Metadata
|
|
- Source: documentation
|
|
- Tags: gitea, api, endpoints, rest
|
|
- Pattern-Key: gitea-api.endpoints
|
|
- Recurrence-Count: 1
|
|
|
|
---
|
|
|
|
<!-- entradas anteriores acima -->
|
|
|
|
## [LRN-20260519-001] clawhub.cli_path
|
|
|
|
**Logged**: 2026-05-19T20:39:00-03:00
|
|
**Priority**: high
|
|
**Status**: pending
|
|
**Area**: config
|
|
|
|
### Summary
|
|
Instalar skills do Clawhub com `/var/lib/openclaw/tools/node/npm/bin/clawhub`
|
|
|
|
### Details
|
|
O CLI `clawhub` não está no `$PATH` global, mas existe instalado em `/var/lib/openclaw/tools/node/npm/bin/clawhub`. Sempre usar o caminho completo.
|
|
|
|
### Suggested Action
|
|
Adicionar symlink ou alias para `clawhub` no PATH do agente, ou sempre usar o caminho completo.
|
|
|
|
### Metadata
|
|
- Source: error
|
|
- Tags: clawhub, cli, path, install
|
|
- Pattern-Key: clawhub.cli_path
|
|
|
|
---
|
|
|
|
## [LRN-20260519-002] clawhub-search-qc
|
|
|
|
**Logged**: 2026-05-19T20:42:00-03:00
|
|
**Priority**: medium
|
|
**Status**: pending
|
|
**Area**: config
|
|
|
|
### Summary
|
|
`clawhub search` com termos muito específicos retorna vazio; usar termos genéricos primeiro
|
|
|
|
### Details
|
|
Pesquisas com termos combinados como "programming developer full-stack" retornaram vazio. Termos simples como "autonomous agent" retornaram resultados. Melhor abordar a pesquisa em múltiplas queries curtas.
|
|
|
|
### Suggested Action
|
|
Fazer múltiplas searches com termos curtos e depois filtrar manualmente.
|
|
|
|
### Metadata
|
|
- Source: error
|
|
- Tags: clawhub, search, query
|
|
- Pattern-Key: clawhub.search_strategy
|
|
|
|
---
|
|
|
|
## [LRN-20260519-003] biblioteca-compartilhada-libs
|
|
|
|
**Logged**: 2026-05-19T21:30:00-03:00
|
|
**Priority**: medium
|
|
**Status**: pending
|
|
**Area**: config
|
|
|
|
### Summary
|
|
Criar biblioteca inteligente compartilhada em `libs/` para reuso entre projetos
|
|
|
|
### Details
|
|
Toda skill instalada tem conhecimento valioso. Centralizar em `libs/<dominio>/`:
|
|
- skills são extraídas e promovidas para arquivos .md limpos
|
|
- novos projetos copiam `libs/` como template
|
|
- o agente consulta `libs/` antes de implementar
|
|
|
|
### Metadata
|
|
- Source: best_practice
|
|
- Tags: biblioteca, reuso, padroes
|
|
- Pattern-Key: libs.shared_knowledge_base
|
|
|
|
---
|
|
|
|
## [LRN-20260519-004] vitest-jsdom-global-mocks
|
|
|
|
**Logged**: 2026-05-19T23:10:00-03:00
|
|
**Priority**: high
|
|
**Status**: applied
|
|
**Area**: testing
|
|
|
|
### Summary
|
|
Vitest jsdom — mocks globais obrigatórios antes de renderizar hooks que usam APIs do navegador
|
|
|
|
### Details
|
|
`localStorage`, `navigator.clipboard`, `window.matchMedia` não existem no jsdom puro — mockar em `beforeAll/beforeEach` global.
|
|
|
|
### Metadata
|
|
- Source: best_practice
|
|
- Tags: vitest, jsdom, mocks, testing
|
|
- Pattern-Key: vitest.jsdom.mocks
|
|
- Recurrence-Count: 3 → promoted
|
|
|
|
---
|
|
|
|
## [LRN-20260519-005] react-testing-library-hooks-async
|
|
|
|
**Logged**: 2026-05-19T23:10:00-03:00
|
|
**Priority**: medium
|
|
**Status**: pending
|
|
**Area**: testing
|
|
|
|
### Summary
|
|
React Testing Library — renderHook + act() para testar hooks assíncronos
|
|
|
|
### Details
|
|
Sempre envolver awaits em `act(async () => { ... })`, nunca acessar `result.current` após await diretamente fora de `act()`.
|
|
|
|
### Metadata
|
|
- Source: best_practice
|
|
- Tags: react, testing-library, hooks, async, vitest
|
|
- Pattern-Key: react.testing-library
|
|
- Recurrence-Count: 3 → promoted
|
|
|
|
---
|
|
|
|
## [LRN-20260520-001] vitest-pure-dom-matchers
|
|
|
|
**Logged**: 2026-05-20T00:52:00-03:00
|
|
**Priority**: high
|
|
**Status**: applied
|
|
**Area**: testing
|
|
|
|
### Summary
|
|
Vitest jsdom puro — matchers nativos DOM funcionam sem `@testing-library/jest-dom`
|
|
|
|
### Details
|
|
Usar `.classList.contains()`, `.getAttribute()`, `.textContent`, `container.querySelector()` ao invés de matchers RTL. 56/56 testes verdes, 0 dependências extras.
|
|
|
|
### Metadata
|
|
- Source: best_practice
|
|
- Tags: vitest, jsdom, testing, matchers
|
|
- Pattern-Key: vitest.pure-dom-matchers
|
|
- Recurrence-Count: 1
|
|
|
|
---
|
|
|
|
## [LRN-20260520-002] jsdom-fireEvent-writable-value
|
|
|
|
**Logged**: 2026-05-20T00:52:00-03:00
|
|
**Priority**: low
|
|
**Status**: workaround
|
|
**Area**: testing
|
|
|
|
### Summary
|
|
No jsdom, `Object.defineProperty(input, 'value', { writable: true })` permite `fireEvent.change`
|
|
|
|
### Details
|
|
Workaround mínimo para testar onChange em inputs no jsdom puro, onde `.value` é readonly.
|
|
|
|
### Metadata
|
|
- Source: error
|
|
- Tags: jsdom, fireEvent, input, value
|
|
- Pattern-Key: jsdom.fireEvent-change-writable
|
|
- Recurrence-Count: 1
|
|
|
|
---
|
|
|
|
## [LRN-20260520-006] portainer-api-ptr-token-scope
|
|
|
|
**Logged**: 2026-05-20T17:38:00-03:00
|
|
**Priority**: high
|
|
**Status**: reference
|
|
**Area**: devops | infra | docker
|
|
|
|
### Summary
|
|
Token Portainer com prefixo `ptr_` é um access token — funciona em `/api/status` (200) mas retorna 401 em `/api/endpoints`, `/api/stacks`, `/api/auth` — escopo muito limitado.
|
|
|
|
### Details
|
|
Para operações de mutação (criar/gerenciar stacks) pela API Portainer é necessário JWT admin válido obtido via `POST /api/auth` com username+senha. Senha admin do Portainer não estava documentada — usamos `docker stack deploy` via CLI Docker Swarm diretamente como workaround.
|
|
|
|
### Suggested Action
|
|
Documentar senha admin do Portainer de forma segura (password manager), ou usar service token JWT com escopo `admin` completo.
|
|
|
|
### Metadata
|
|
- Source: error
|
|
- Tags: portainer, api, token, docker-swarm, jwt
|
|
- Pattern-Key: portainer.api-ptr-token-scope
|
|
|
|
---
|
|
|
|
## [LRN-20260520-007] docker-compose-v3-swarm-labels-and-restart-policy
|
|
|
|
**Logged**: 2026-05-20T17:38:00-03:00
|
|
**Priority**: high
|
|
**Status**: reference
|
|
**Area**: devops | docker
|
|
|
|
### Summary
|
|
docker stack deploy com compose v3.9 no Swarm mode — duas propriedades que não funcionam como esperado:
|
|
|
|
1. **`restart_policy`** — não é propriedade válida no `deploy` spec; Docker Swarm gerencia restart nativamente (Always)
|
|
2. **`deploy.labels`** — não se tornam container labels; labels só são aplicadas via `docker service create --label-add` ou `--label-add` em `docker service update`
|
|
|
|
### Fix
|
|
- Remover `restart_policy` do YAML
|
|
- Aplicar labels necessárias (ex: caddy=) via CLI `docker service update --label-add` ou diretamente no `docker service create`
|
|
|
|
### Metadata
|
|
- Source: error
|
|
- Tags: docker, swarm, compose, labels, restart-policy
|
|
- Pattern-Key: docker.swarm-compose-v3-gotchas
|
|
|
|
---
|
|
|
|
## [LRN-20260520-008] stack-migration-docker-swarm-createmigrate
|
|
|
|
**Logged**: 2026-05-20T17:38:00-03:00
|
|
**Priority**: medium
|
|
**Status**: reference
|
|
**Area**: devops | docker
|
|
|
|
### Summary
|
|
Migração de stack Docker Swarm (trocar de nome/namespace):
|
|
1. Criar novos serviços com o nome da stack destino + labels corretas (`docker service create --label-add ...`)
|
|
2. Validar que os serviços estão saudáveis
|
|
3. Remover a stack antiga (`docker stack rm <antiga>`)
|
|
|
|
### Safety notes
|
|
- Não usar `docker stack deploy --prune` em stack ativa em produção — apaga services não presentes no novo compose
|
|
- Para imagens locais: usar `--with-registry-auth` ou tag com registry acessível
|
|
- Sempre validar `docker service ps <nome>` antes de remover a stack antiga
|
|
|
|
### Metadata
|
|
- Source: best_practice
|
|
- Tags: docker, swarm, migration, stack
|
|
- Pattern-Key: docker.swarm-stack-migration
|
|
|
|
|
|
## LRN-20260520-010 — Stack Portainer API Attachable=false bloqueia deploy
|
|
|
|
**Categoria**: knowledge_gap
|
|
|
|
### Problema
|
|
- Rede `public` tem `Attachable: false`
|
|
- `docker stack deploy` CLI FUNCIONA mesmo assim (Daemon local ignora)
|
|
- Portainer API BLOQUEIA (Docker Remote API respeita `Attachable=false`)
|
|
- Erro: `failed to set up container networking: Could not attach to network public: rpc error: code = PermissionDenied desc = network public not manually attachable`
|
|
|
|
### Decisão tomada
|
|
Stack `project` fica gerenciada por `docker stack deploy` CLI (Swarm nativo).
|
|
Portainer controla apenas stacks 1,2,4,6,7,8,9 (com SwarmId preenchido).
|
|
Stack `project` não é controlável pelo Portainer enquanto `Attachable=false`.
|
|
|
|
### Para resolver completamente
|
|
Recriar rede `public` com `--attachable` impactando 19 containers — NÃO recomendado agora.
|
|
|
|
|
|
## [LRN-20260520-011] atomic-design-3d-threejs-landing-page
|
|
|
|
**Logged**: 2026-05-20T20:00:00-03:00
|
|
**Priority**: high
|
|
**Status**: reference
|
|
**Area**: frontend | web3d | design-system
|
|
|
|
### Summary
|
|
Landing page 3D completa usando Atomic Design + Three.js + React Three Fiber + Framer Motion — código funcional com build OK.
|
|
|
|
### Padrão aplicado
|
|
```
|
|
Átomos (11) → Moléculas (3) → Organismos (2) → Templates (2) → Pages (1)
|
|
```
|
|
|
|
**Tokens 8 domínios**: space / font / color / shadow / radius / material3d / camera3d / animation
|
|
|
|
### Stack
|
|
- Vite + React 18 + TS
|
|
- @react-three/fiber + drei
|
|
- framer-motion
|
|
- Build: `npm install && npm run build` ✅
|
|
|
|
### Decisões
|
|
- Canvas fullscreen com overlay 2D (SceneCanvas + ThreePage template)
|
|
- ScrollControls → scroll move câmera 3D
|
|
- Material tokens: roughness 0.08 + metalness 0.75 = super-reflexivo
|
|
- PointLights accent + secondary por cena
|
|
- CSS vars para dark/light mode + A11y
|
|
|
|
### Hot Reload (future)
|
|
- `npm run dev` → Vite HMR
|
|
- Adicionar ao stack Swarm `dev` (volume bind mount + dnsrr)
|
|
|
|
|
|
## [LRN-20260520-012] proxy-test-octal-deploy-zero-downtime
|
|
|
|
**Logged**: 2026-05-20T20:15:00-03:00
|
|
**Priority**: high
|
|
**Status**: reference
|
|
**Area**: devops | docker | caddy
|
|
|
|
### Problema
|
|
test.octal.tec.br estava com HTML estático antigo (Octal Technology landing page). Precisou atualizar para o Pulse 3D.
|
|
|
|
### Solução aplicada
|
|
1. Extrair HTML atual da imagem `test-octal:latest` via `docker cp`
|
|
2. Substituir pelo HTML novo (Pulse 3D — CSS completo)
|
|
3. Build: `docker build -t test-octal:latest .` no diretório com HTML + Dockerfile
|
|
4. Deploy: `docker service update --image test-octal:latest proxy_test-octal`
|
|
5. Resultado: zero-downtime, serviço convergiu com imagem nova
|
|
|
|
### Padrão
|
|
```bash
|
|
# Extrair atual
|
|
mkdir /tmp/site-src && docker cp container:/usr/share/nginx/html . /tmp/site-src/
|
|
# Atualizar index.html
|
|
cp novo.html /tmp/site-src/index.html
|
|
# Reconstruir
|
|
cd /tmp/site-src && cat > Dockerfile << 'D'
|
|
FROM nginx:alpine
|
|
COPY index.html /usr/share/nginx/html/index.html
|
|
EXPOSE 80
|
|
CMD ["nginx","-g","daemon off;"]
|
|
D
|
|
docker build -t test-octal:latest . && docker push registry.octal.tec.br/pulse/test-octal:latest
|
|
# Atualizar service
|
|
docker service update --image test-octal:latest proxy_test-octal
|
|
```
|
|
|
|
### Resultado
|
|
- https://test.octal.tec.br agora mostra Pulse 3D Landing
|
|
- Registry push pode falhar com SSL (registry local com cert autoassinado) — usar imagem local diretamente no `docker service update` funciona
|