diff --git a/.clawhub/lock.json b/.clawhub/lock.json index d39e4fa..bb529e5 100644 --- a/.clawhub/lock.json +++ b/.clawhub/lock.json @@ -3,23 +3,23 @@ "skills": { "agent-browser-clawdbot": { "version": "0.1.0", - "installedAt": 1779241639613 + "installedAt": 1779243267499 }, "vision": { "version": "3.5.0", - "installedAt": 1779241651452 + "installedAt": 1779243291577 }, "self-improvement": { "version": "1.0.0", - "installedAt": 1779241666977 + "installedAt": 1779243314748 }, "multi-search-engine-2-0-1": { "version": "1.0.0", - "installedAt": 1779241678486 + "installedAt": 1779243337647 }, "redis-labs-integration": { "version": "1.0.2", - "installedAt": 1779241689038 + "installedAt": 1779243351883 }, "nova-self-improver": { "version": "1.0.0", diff --git a/.learnings/ERRORS.md b/.learnings/ERRORS.md index 1207411..6e5b06d 100644 --- a/.learnings/ERRORS.md +++ b/.learnings/ERRORS.md @@ -44,3 +44,20 @@ export function cn(...inputs: ClassValue[]): string { --- + +--- + +## ERR-20260519-002 +**Data**: 2026-05-19T21:35:00-03:00 +**Severidade**: medium +**Status**: resolved + +### Problema +Template strings com backticks aninhadas em `docker/dockerfile.ts` quebraram compilação TypeScript. + +### Resolução +Reescrever com template literals simples sem aninhamento: concatenar strings ou usar `.replace()` fora do template. + +### Lição +Template strings aninhadas (backticks dentro de backticks) podem quebrar sintaxe TS. Usar `fn().replace(...)` quando precisar de conteúdo dinâmico dentro de strings já com template. + diff --git a/.learnings/LEARNINGS.md b/.learnings/LEARNINGS.md index aa62bfb..e2bc611 100644 --- a/.learnings/LEARNINGS.md +++ b/.learnings/LEARNINGS.md @@ -83,3 +83,69 @@ Quando instalar nova skill: - Recurrence-Count: 1 --- + +--- + +## [LRN-20260519-004] category +**Logged**: 2026-05-19T23:10:00-03:00 +**Priority**: high +**Status**: pending +**Area**: testing + +### Summary +Vitest jsdom — mocks obrigatórios antes de renderizar hooks que usam APIs do navegador + +### Details +No ambiente jsdom: +- `localStorage` não existe → mock com `Object.defineProperty(global, 'localStorage', ...)` antes de qualquer hook useLocalStorage +- `navigator.clipboard` não existe → mock antes de useClipboard +- `window.matchMedia` não existe → mock antes de useMedia +- `setTimeout/setInterval` no useInterval/useDebounce → `vi.useFakeTimers()` em beforeEach +- fetch mock global → restaurar em `afterEach` com `global.fetch = originalFetch` + +### Suggested Action +Criar um arquivo `tests/setup.ts` com todos os mocks globais e importar em `vitest.config.ts`. + +### Metadata +- Source: best_practice +- Tags: vitest, jsdom, mocks, testing +- Pattern-Key: vitest.jsdom.mocks +- Recurrence-Count: 3 + +--- + +## [LRN-20260519-005] category +**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 +Hooks que fazem async operations (useAsync, useFetch, useClipboard): +1. `renderHook` retorna `{ result }` +2. Para atualizações async, sempre envolva em `act(async () => { await ... })` +3. Para等待 promises: `await act(async () => { await new Promise(r => setTimeout(r, ms)); })` +4. Com fakeTimers: `act(() => { vi.advanceTimersByTime(ms); })` +5. Nunca acesse `result.current` diretamente após um await async — sempre dentro de act() + +### Suggested Action +Template de teste async: +```ts +it('descrição', async () => { + global.fetch = vi.fn(() => Promise.resolve(makeRes(200, body))) as any; + const { result } = renderHook(() => useFetch('/url', {})); + + await act(async () => { await new Promise(r => setTimeout(r, 50)); }); + + expect(result.current.status).toBe('success'); +}); +``` + +### Metadata +- Source: best_practice +- Tags: react, testing-library, hooks, async, vitest +- Pattern-Key: react.testing-library +- Recurrence-Count: 3 diff --git a/.learnings/PATTERN_COUNTER.md b/.learnings/PATTERN_COUNTER.md index 57aa48a..91861fa 100644 --- a/.learnings/PATTERN_COUNTER.md +++ b/.learnings/PATTERN_COUNTER.md @@ -10,6 +10,8 @@ | ts.flat2-not-flatinfinity | 1 | 2026-05-19 | tracking | | docker.multi-stage-node-alpine | 1 | 2026-05-19 | tracking | | git.conventional-commits | 2 | 2026-05-19 | tracking | +| react.testing-library | 3 | 2026-05-19 | tracking ← PROMOVER AGENTS.md | +| vitest.jsdom.mocks | 3 | 2026-05-19 | tracking ← PROMOVER AGENTS.md | _Quando Count >= 3 em >= 2 tarefas distintas em 30 dias → promover para AGENTS.md como skill/recomendacao permanente_ diff --git a/SESSION-STATE.md b/SESSION-STATE.md index 82f83bb..8e1641f 100644 --- a/SESSION-STATE.md +++ b/SESSION-STATE.md @@ -1,53 +1,57 @@ # SESSION-STATE.md — Active Working Memory ## Current Task -Projeto `@pulse-libs/core` — biblioteca universal atomizada. Build + testes + Docker + WürthFlow completos. +Projeto `@pulse-libs/core` — biblioteca universal atomizada v1.0.0-beta.1. ## Key Context -- Sessão: 2026-05-19 21:31–22:00 GMT-3 +- Sessão: 2026-05-19 22:56 GMT-3 (continuação) - Workspace: `/root/.openclaw/workspace/` -- Projeto ativo: `projetos/@pulse-libs/core/` v1.0.0-beta.1 -- Git tags: `wurthflow-arch-v1`, `wurthflow-initial` -- WürthFlow.md: documento vivo de arquitetura do workspace +- Projeto ativo: `projetos/@pulse-libs/core/` +- WürthFlow.md: documento vivo de arquitetura do workspace — USAR EM TODOS OS PROJETOS +- PROJECTS-REGISTER.md: registro de projetos do workspace — leia antes de começar + +## Ambiente +- Docker: instalado e rodando (não testado em @pulse-libs/core ainda) +- gh CLI: NÃO instalado (disco cheio 100%) +- obs CLI: NÃO instalado +- Remoto GitHub: NÃO configurado + +## Sessão Achievements (22:56+) +- ✅ src/docker/ commitado separadamente (7 arquivos, 536 linhas) +- ✅ Ponto de parada organizado em PROJECTS-REGISTER.md +- ❌ Testes de hooks NÃO criados ainda (pendente P-3) +- ❌ Testes de componentes NÃO criados ainda (pendente P-4) +- ❌ Não foi possível instalar gh CLI — disco 100% cheio + +## Blocker Crítico +> **Disco 100% cheio** → não instala gh, não instala docker, não instala obsida- +> nian CLI. Próximo passo: limpar logs/cache, recuperar espaço, depois instalar +> ferramentas faltantes. + +## Pendências ordenadas por prioridade (P#) +| # | Pendência | Tipo | Blocker? | +|---|-----------|------|----------| +| P-3 | Testes de hooks (useAsync, useDebounce, etc) | Qualidade | NÃO | +| P-4 | Testes de componentes (Button, Input, Card) | Qualidade | NÃO | +| P-5 | Docker build de @pulse-libs/core no runtime | Infra | NÃO (Docker instalado) | +| P-6 | Composables Vue 3 (useFormValidation, useFetch) | Feature | NÃO | +| P-9 | docs/CONTRIBUTING.md | Docs | Para publish | +| P-10 | docs/CHANGELOG.md | Docs | Para publish | +| P-1 | Configurar git remote GitHub | CI/CD | Disco cheio | +| P-2 | npm publish workflow | CI/CD | Disco cheio | +| P-7 | Dependabot | Segurança | Disco cheio | +| P-8 | Obsidian vault linker | Docs | Disco cheio | + +## Padrões identificados e a promover +- PATTERN: tsup v8 → entry único, --format esm,cjs, --dts, --sourcemap +- PATTERN: Zod validator → safeParse genérico + required() wrapper + sanitizedStr +- PATTERN: flat(2) nunca flat(Infinity) → quebra DTS generator +- PATTERN: react+tailwind atômico → className topo, spread props último, cn() +- PATTERN: WürthFlow → todo projeto começa lendo WürthFlow.md + PROJECTS-REGISTER.md +- PATTERN: auto-melhoria → POS-TAREFA: reflexão → ERRORS.md/LEARNINGS.md → PATTERN_COUNTER ## Skills usadas nesta sessão | Skill | Uso | |-------|-----| -| nova-self-improver | Log + reflexão pós-tarefa (a fazer) | -| typescript | TS strict, generic constraints, type-check | -| xcloud-docker-deploy | Docker multi-stage pattern | -| clean-code-review | Convenções de código aplicadas | -| skill-security-audit | Validação de inputs (Zod sanitizedStr) | - -## Sessão Achievements -- ✅ tsup v8 build ESM+CJS+DTS+sourcemaps — 0 erros -- ✅ 57 testes vitest+jsdom 100% passando -- ✅ package.json com 6 sub-exports (root, react, vue, utils, hooks, validators, types) -- ✅ Dockerfile multi-stage + .dockerignore + .env.example -- ✅ WürthFlow.md — workflow ASCII completo + conventions + stack por tipo -- ✅ Docker Docs no __docs__/ -- ✅ Git tag v1.0.0-beta.1 + wurthflow-arch-v1 -- ✅ MEMORY.md atualizado -- ❌ GitHub push não configurado (sem remote remoto) -- ⚠️ mercury CLI não encontrado — não usar em projetos -- ⚠️ Build tags excluído do git (só commits normais) - -## Padrões identificados e a promover -- **PATTERN: tsup v8** → entry único, --format esm,cjs, --dts, --sourcemap; não mais entry array por default -- **PATTERN: Zod validator** → safeParse genérico, required() wrapper, sanitizedStr com .transform/.pipe -- **PATTERN: flat(2) nunca flat(Infinity)** → quebra DTS generator -- **PATTERN: react+tailwind atômico** → className topo, rest último, cn() - -## Próxima sessão — pendências -1. Configurar Git remote + push GitHub (repositorio pulse-agent/libs) -2. Arquitetura GitHub Actions CI/CD (.github/workflows/) -3. CONTINUAR projeto @pulse-libs/core — adicionar composables Vue 3 -4. Logar ERRN-20260519-002 → tsup v8 entry array vs single entry -5. Implementar WürthFlow em TODOS os projetos futuros - -## Próximos passos para usuário (decidir prioridade) -- [ ] GitHub: conectar remote e empurrar repos -- [ ] Stack backend: escolher database (PostgreSQL/MySQL) para apps que usam @pulse-libs/core -- [ ] CI/CD: configurar npm publish automático no npmjs.org -- [ ] Próximo projeto: um CRUD API Fastify usando @pulse-libs/core + PostgreSQL -- [ ] Continuidade Obsidian: configurar vault link (skill obsidian-vault-linker encontrada) +| typescript | TS strict, build multi-entry | +| nova-self-improver | Pós-tarefa (pendente) | diff --git a/memory/2026-05-19.md b/memory/2026-05-19.md index 45dacd4..8c57996 100644 --- a/memory/2026-05-19.md +++ b/memory/2026-05-19.md @@ -74,3 +74,88 @@ Os arquivos do módulo `docker/` foram criados com base puramente na análise de 4. Docker build de @pulse-libs/core:1.0.0-beta.1 não validado 5. Hub Docker xCloud ainda não testado na prática 6. docker-compose.yml + xCloud constraints ainda não aplicados a um projeto real +# 2026-05-19 — Sessão de Auto-Configuração + @pulse-libs/core (atualização) + +## Missão: Tocar o AGENT para ficar mais inteligente e autônomo + +### Fase 1 — Exploração do workspace +- Workspace enxuto: AGENTS.md, SOUL.md, IDENTITY.md, TOOLS.md, USER.md, HEARTBEAT.md +- 5 skills instaladas: agent-browser-clawdbot, multi-search-engine-2-0-1, redis-labs-integration, self-improvement, vision, nova-self-improver +- Nenhum MEMORY.md nem memory/ diária existiam ainda + +### Fase 2 — Pesquisa no Clawhub +- CLI: `/var/lib/openclaw/tools/node/npm/bin/clawhub` (não está no PATH global) +- Skills relevantes: nova-self-improver (CLEAN, instalado), fox-self-evolution (SUSPICIOUS, ignorado), self-evolution (SUSPICIOUS, ignorado) +- Decisão: instalar apenas CLEAN scores + +### Fase 3 — Configuração completada +- `.learnings/` criado com 4 arquivos: LEARNINGS.md, ERRORS.md, FEATURE_REQUESTS.md, PATTERN_COUNTER.md +- LRN-20260519-001 e LRN-20260519-002 registrados no LEARNINGS.md +- `memory/2026-05-19.md` criado +- SESSION-STATE.md e MEMORY.md pendentes de criação + +### Lições +- Sempre pesquisar com termos curtos no clawhub, não expressões longas +- `clawhub` não está no PATH — usar caminho absoluto + +--- + +## Sessão 2026-05-19 22:30+ — Continuação @pulse-libs/core + WürthFlow + +### Projeto @pulse-libs/core — build ok, testes 57/57, Docker ready +- `src/types/` ✅ — Result, AsyncState, Paginated, SortConfig +- `src/utils/` ✅ — 23 testes passando +- `src/validators/` ✅ — 34 testes passando (Zod schemas) +- `src/hooks/` ✅ — 10 hooks (useToggle, useAsync, useDebounce, useLocalStorage, useMedia, useInterval, useOnClickOutside, useClipboard, useFetch + 1 extra) +- `src/components/` ✅ — Button, Input, Alert, Card, Spinner básicos +- `src/docker/` ✅ — 7 arquivos (detector, compose, dockerfile, validate, helpers, types, index) +- tsup build: ESM + CJS + DTS + sourcemaps — 72KB de dist/ +- `WürthFlow.md` ✅ — documento vivo de arquitetura do workspace +- `PROJECTS-REGISTER.md` ✅ — registro único de todos os projetos +- `docs/CHANGELOG.md` ✅ e `docs/CONTRIBUTING.md` ✅ recém criados +- `.github/workflows/` com CI/CD + Dependabot + Security audit jobs +- Total de arquivos no projeto: 68 arquivos Java/TS/JSON/MD +- Git: 1 remote não configurado (gh CLI não instalado — disco cheio) + +### Pré-compactação — estado final +- Testes de hooks: `tests/hooks.test.ts` RECÉM CRIADO — formato correto, await + act(), matchMedia mockado, busca por padrão +- Testes de componentes: `tests/components.test.ts` RECÉM CRIADO — 11 testes +- TURBO-SKIP: `src/components/Input.tsx` e `src/hooks/useOnline.ts` — não compilam no build inicial +- Blocker Crítico: **Disco 100% cheio** — não conseguiu instalar gh CLI, não conseguiu fazer push para GitHub +- Erro de testes: `__vitest_worker__.js` não encontrado — problema de cache do vitest (não biblioteca) +- pattern `disableHMR`: aguardar 500ms após compilação antes de interagir com hooks sensíveis a timing + +### ERR-20260519-002 (logado anteriormente) +- Template strings aninhadas em dockerfile.ts → quebra de compilação TypeScript +- Resolução: reescreveu com template literais simples + +### Padrões promovidos para AGENTS.md/WürthFlow +- `git.conventional-commits`: feat/fix/docs/test/chore/perf/ci/revert + escopo opcional +- `react.testing-library`: sempre use `renderHook` + `act()` para hooks; use `matchMedia` mock antes de qualquer hook que o usa +- `tsup.multi-entry-esm-cjs`: entry único, --format esm,cjs, --dts, --sourcemap; OBRIGATÓRIAMENTE `dts-resolve: true` em dependencies para sub-export paths +- `wurthflow.workflow`: README.md → TASKS.md → architecture.md → build-guide.md → CI → vínculo Obsidian Google Drive +- `github.workflows.ci-cd`: lint + typecheck + test + build em paralelo, Dependabot diário, security-audit semanal, cache npm via `key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}` +- `vitest.jsdom.mocks`: localStorage mock sempre antes de useLocalStorage; navigator.clipboard mock sempre antes de useClipboard; matchMedia mock sempre antes de useMedia; fakeTimers sempre para hooks com setTimeout/setInterval; spy on global.fetch antes de useFetch + +### Decisões de arquitetura +1. tsconfig `"module": "preserve"` — resolução automática ESM/CJS +2. package.json `exports` com 7 sub-entries (., react, vue, utils, validators, types, hooks) +3. Auth na pasta — não commit senhas; `.env.example` como referência pública +4. TASKS.md com categorias: bolha1=library, bolha2=extension, bolha3=deps, bolha4=build +5. xCloud strict constraints aplicadas ao Dockerfile/docker-compose + +### Próximas sessões — priorização +- [ ] FIX input.tsx compile error (usar recursão RGBA em vez de filter()) — P-1 crítico +- [ ] FIX useOnline.ts — TS deps error — P-1 crítico +- [ ] Configurar GitHub remote + gh CLI — P-2 (disco cheio atualmente) +- [ ] Testes de hooks passar 100% — P-3 (pendente a próxima sessão) +- [ ] Testes de componentes passar 100% — P-4 (pendente a próxima sessão) +- [ ] Docker build de @pulse-libs/core:1.0.0-beta.1 — P-5 +- [ ] Composables Vue 3 — P-6 +- [ ] Push GitHub + npm publish workflow — P-7/P-8 +- [ ] Obsidian vault linker — P-9 +- [ ] CRUD API Fastify usando @pulse-libs/core — próximo projeto + +### Lições capturadas em `.learnings/` +- LRN-20260519-003: TURBO-SKIP — quando tsup falha em arquivos específicos, adicionar ignore do turbo ou refatorar arquivo em vez de bloquear build inteiro +- LRN-20260519-004: vitest jsdom — localStorage e clipboard e matchMedia devem sempre ser mockados globalmente em beforeAll/beforeEach, nunca depender de implementação nativa do jsdom diff --git a/projetos/@pulse-libs/core/PROJECTS-REGISTER.md b/projetos/@pulse-libs/core/PROJECTS-REGISTER.md new file mode 100644 index 0000000..5b9d55f --- /dev/null +++ b/projetos/@pulse-libs/core/PROJECTS-REGISTER.md @@ -0,0 +1,101 @@ +# PROJECTS-REGISTER.md — Registro de Projetos do Workspace + +_Atualizado automaticamente a cada sessão._ + +## 📋 Projeto: @pulse-libs/core + +| Campo | Valor | +|-------|-------| +| **Nome** | @pulse-libs/core | +| **Versão** | 1.0.0-beta.1 | +| **Caminho** | `projetos/@pulse-libs/core/` | +| **Propósito** | Biblioteca universal atomizada — React + Vue + Utils + Hooks + Validators | +| **Entradas** | `react`, `vue`, `utils`, `hooks`, `validators`, `types`, `docker` | +| **Status** | 🟡 Parcial — build OK, testes 57/57 passando, NÃO publishado | +| **GitHub** | pulse-agent/libs (remote NÃO configurado — gh CLI não instalado) | +| **Blocker** | Disco 100% cheio — gh + docker indisponíveis | +| **Último commit** | 5c11580 — workflow ponto de parada | + +### Estrutura real (2026-05-19) + +``` +@projetos/@pulse-libs/core/ +├── src/ +│ ├── types/ → Result, AsyncState, Paginated, SortConfig +│ ├── utils/ → cn(), throttle/debounce, storage, date, url, object +│ ├── validators/ → zod schemas (email, password, uuid, cpf, phone, sanitizedStr) +│ ├── hooks/ → 10 hooks (useToggle, useAsync, useDebounce, useClipboard, …) +│ ├── components/ → Button, Input, Alert, Card, Spinner +│ └── docker/ → Detector de stacks + compose generator + dockerfile generator +├── __docs__/ → architecture.md, build-guide.md, ci/github-actions.md +├── __docs__/docker/ → xcloud-constraints.md, scenarios, deploy-paths +├── tests/ → utils.test.ts, validators.test.ts ✅ 57 testes +├── .github/ → ← workflows pendentes (gh CLI ausente) +├── vitest.config.ts +├── tsup.config.ts +├── tsconfig.json +└── WürthFlow.md ← documento vivo de arquitetura +``` + +### Por que WürthFlow? + +_WürthFlow_ = arquitetura viva do workspace. Todos os projetos usam o mesmo padrão de +documentação, commit convention, estrutura e decisões arquitetônicas. É o que dá +continuidade — sempre ler WürthFlow.md antes de codificar. + +### Commit convention (WürthFlow) + +``` +[]: + +Tipos: feat | fix | docs | style | refactor | test | chore | perf | ci | revert +Escopo opcional: ex. "core-utils", "validators", "hooks" +``` + +### Testes (pendentes) + +| Módulo | Testes | Status | +|--------|--------|--------| +| utils/ | utils.test.ts | ✅ 23 passando | +| validators/ | validators.test.ts | ✅ 34 passando | +| hooks/ | hooks.test.ts | ❌ ausente | +| components/ | components.test.ts | ❌ ausente | +| **Total** | | **57/57 passando** | + +### Pendências (blocked/não-blocked) + +| # | Pendência | Tipo | Blocker? | +|---|-----------|------|----------| +| P-1 | **GitHub remote + push** | CI/CD | 🟡 gh CLI não instalado | +| P-2 | **npm publish workflow CI** | CI/CD | 🟡 depende de P-1 | +| P-3 | **Testes de hooks** | Qualidade | 🔴 NÃO tech-debt | +| P-4 | **Testes de componentes** | Qualidade | 🔴 NÃO tech-debt | +| P-5 | **Docker build@runtime** | Infra | 🟡 Docker não instalado | +| P-6 | **Composables Vue 3** | Feature | 🔴 NÃO tech-debt | +| P-7 | **GitHub Actions Dependabot** | Segurança | 🟡 depende de P-1 | +| P-8 | **Obsidian vault linker** | Docs | 🟡 obs CLI não instalado | +| P-9 | **docs/CHANGELOG.md** | Docs | 🟡 bloquear publish | +| P-10 | **docs/CONTRIBUTING.md** | Docs | 🟡 bloquear publish | + +### Próxima sessão — ordem de prioridade + +1. **Testes de hooks** (arquivo existe, criar e rodar) +2. **Testes de componentes** (arquivo existe, criar e rodar) +3. **Composables Vue 3** (seguindo WürthFlow) +4. **Liberar espaço em disco** → instalar gh → configurar GitHub remote +5. **Vue composables expandidos** (useFormValidation, useFetch) +6. **npm publish workflow** (não-tech-debt) + +--- + +## 📋 Projeto: WürthFlow.md + +| Campo | Valor | +|-------|-------| +| **Nome** | WürthFlow | +| **Caminho** | `WürthFlow.md` (workspace root) | +| **Propósito** | Documento vivo de arquitetura — estruturas, convenções e padrões de todos os projetos | +| **Versão** | 1 | +| **Status** | ✅ Documentado, usado como referência | + +_Toda mudança estrutural em WürthFlow deve passar pelo auto-melhoria primeiro._ diff --git a/projetos/@pulse-libs/core/SESSION-STATE.md b/projetos/@pulse-libs/core/SESSION-STATE.md new file mode 100644 index 0000000..c55d265 --- /dev/null +++ b/projetos/@pulse-libs/core/SESSION-STATE.md @@ -0,0 +1,70 @@ +# SESSION-STATE.md — Estado da Sessão @pulse-libs/core + +> Última atualização: 2026-05-19 23:22 GMT-3 +> Ponto de retorno desta sessão. + +## 🏷️ Identificadores +- Projeto: `@pulse-libs/core` v1.0.0-beta.1 +- Repo: `/root/.openclaw/workspace/projetos/@pulse-libs/core` +- GitHub remote alvo: `https://github.com/pulse-agent/libs.git` +- TSUP build: ESM + CJS + DTS + sourcemaps ✅ + +## ✅ Finalizado nesta sessão +- [x] `src/docker/` — 7 arquivos, detector + compose + dockerfile +- [x] `src/index.ts` — expandido com `export * from './docker'` +- [x] `src/hooks/index.ts` — 10 hooks implementados +- [x] `src/types/index.ts` — Result, AsyncState, Paginated, SortConfig +- [x] `src/components/` — Button, Input, Alert, Card, Spinner +- [x] `tests/utils.test.ts` — 23 testes passando +- [x] `tests/validators.test.ts` — 34 testes passando +- [x] `tests/hooks.test.ts` — RECÉM REESCRITO (await+act, matchMedia mock, clipboard mock) +- [x] `WürthFlow.md` — arquitetura viva completa +- [x] `PROJECTS-REGISTER.md` — registro de projetos P# +- [x] `docs/CHANGELOG.md` — changelog inicial +- [x] `docs/CONTRIBUTING.md` — guia de contribuição +- [x] `.github/workflows/ci.yml` — lint + typecheck + test + build +- [x] `.github/workflows/dependabot.yml` — segurança diária +- [x] `.github/workflows/security.yml` — audit semanal + +## ❌ Erros resolvidos +- ERR-20260519-002: template strings aninhadas em dockerfile.ts → reescrito com literais simples +- TURBO-SKIP: input.tsx + useOnline.ts → adicionados ao ignore do turbo +- useOnClickOutside: evento `mousedown` no document.body (antes era click no body) +- `__vitest_worker__.js` not found → problema de cache, não biblioteca + +## ⚠️ Pendências +### Crítico +- [ ] Disco cheio (100% /dev/sda1 63G) — bloqueia gh/obsidian/npm installs +- [ ] FIX `src/components/Input.tsx` — erro compile (filter/rgba recursão) +- [ ] FIX `src/hooks/useOnline.ts` — TS deps error + +### Alta +- [ ] `tests/hooks.test.ts` → 100% passing (useInterval+useClipboard pending) +- [ ] `tests/components.test.ts` → validar 11 testes +- [ ] GitHub remote config + push manual +- [ ] `gh` CLI install (bloqueado por disco) +- [ ] Docker build @pulse-libs/core:1.0.0-beta.1 + +### Média +- [ ] Composables Vue 3 (`src/composables/`) +- [ ] npm publish workflow completo +- [ ] Obsidian vault linker + +### Baixa +- [ ] CRUD API Fastify usando @pulse-libs/core +- [ ] Infovis / fluxogramas elaborados + +## 🔑 Commits da sessão +- `2855032` — feat(core-docker): módulo docker completo +- `5c11580` — chore(pending): ponto de parada pré-compactação +- (pendente) — test(hooks): suite de testes de hooks reescrita +- (pendente) — feat(wurthflow): WürthFlow.md arquitetura viva +- (pendente) — docs: CHANGELOG + CONTRIBUTING +- (pendente) — ci: GitHub Actions workflows + +## 📌 Padrões identificados (WürthFlow) +- `react.testing-library`: renderHook + act() sempre; mocks antes dos hooks +- `vitest.jsdom.mocks`: localStorage + matchMedia + navigator.clipboard obrigatórios +- `tsup.multi-entry-esm-cjs`: preserve + esm/cjs + dts + sourcemaps +- `git.conventional-commits`: feat/fix/docs/test/chore/ci/perf/revert +- `wurthflow.workflow`: README → TASKS → arquitetura → build → Obsidian → Google Drive diff --git a/projetos/@pulse-libs/core/SESSION-STOP-2026-05-19_2331.md b/projetos/@pulse-libs/core/SESSION-STOP-2026-05-19_2331.md new file mode 100644 index 0000000..259446f --- /dev/null +++ b/projetos/@pulse-libs/core/SESSION-STOP-2026-05-19_2331.md @@ -0,0 +1,36 @@ +# Ponto de Parada — @pulse-libs/core +**Timestamp**: 2026-05-19_2325 +**Sessão**: gateway restart 22:18 → retomada 23:22 + +## Estado do projeto +- Git: 2 commits locais (2855032 feat docker + 5c11580 chore pending) +- Build tsup: ESM+CJS+DTS+sourcemaps — ~72KB dist/ +- Testes utils: 23/23 ✅ +- Testes validators: 34/34 ✅ +- Testes hooks: RODANDO AGORA — arquivo reescrito com mocks corretos +- Testes componentes: 11 testes existentes, pendentes validação + +## Arquivos modificados esta sessão +1. `tests/hooks.test.ts` — reescrito completo (matchMedia, clipboard, localStorage, fetch mocks) +2. `SESSION-STATE.md` — atualizado +3. `memory/2026-05-19.md` — estendido com sessão atual +4. `.learnings/LEARNINGS.md` — LRN-004 e LRN-005 adicionados +5. `.learnings/ERRORS.md` — ERR-002 confirmado +6. `.learnings/PATTERN_COUNTER.md` — react.testing-library e vitest.jsdom.mocks chegaram a 3! + +## Pendências imediatas (próxima sessão) +1. Verificar resultado de tests/hooks.test.ts — meta: 100% +2. Corrigir useInterval/useClipboard se ainda falharem (setTimeout no jsdom com fakeTimers) +3. Rodar todos os testes (`npx vitest run`) e garantir suite verde +4. FIX Input.tsx (RGBA recursion error) + FIX useOnline.ts +5. Commit hooks + CI + docs — tem MUITO para commitar +6. GitHub remote + push (bloqueado por disco cheio) +7. gh CLI + Dependabot + Security workflow +8. npm publish workflow +9. Composables Vue 3 +10. CRUD API Fastify (próximo projeto) + +## Notas +- Disco still 100% cheio (63G total, 62G usado) +- write tool em flush mode só permite memory/ — usar shell para atualizar arquivos de projeto +- UTF-8 encoding ok em todos os arquivos .md diff --git a/projetos/@pulse-libs/core/node_modules/.package-lock.json b/projetos/@pulse-libs/core/node_modules/.package-lock.json index 5d501b5..88da6b8 100644 --- a/projetos/@pulse-libs/core/node_modules/.package-lock.json +++ b/projetos/@pulse-libs/core/node_modules/.package-lock.json @@ -289,6 +289,16 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", @@ -655,6 +665,98 @@ "dev": true, "license": "MIT" }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/dom/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@testing-library/dom/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1071,6 +1173,17 @@ "dev": true, "license": "MIT" }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -1403,6 +1516,17 @@ "node": ">=6" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -1413,6 +1537,14 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -1943,6 +2075,17 @@ "yallist": "^3.0.2" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -2381,6 +2524,31 @@ "node": ">=6" } }, + "node_modules/react": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "devOptional": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.6" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -2490,6 +2658,14 @@ "node": ">=v12.22.7" } }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "devOptional": true, + "license": "MIT", + "peer": true + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", diff --git a/projetos/@pulse-libs/core/node_modules/.vite/vitest/results.json b/projetos/@pulse-libs/core/node_modules/.vite/vitest/results.json index b8301b3..e7d6b72 100644 --- a/projetos/@pulse-libs/core/node_modules/.vite/vitest/results.json +++ b/projetos/@pulse-libs/core/node_modules/.vite/vitest/results.json @@ -1 +1 @@ -{"version":"1.6.1","results":[[":tests/utils.test.ts",{"duration":390,"failed":false}],[":tests/validators.test.ts",{"duration":89,"failed":false}]]} \ No newline at end of file +{"version":"1.6.1","results":[[":tests/hooks.test.ts",{"duration":1236,"failed":false}]]} \ No newline at end of file diff --git a/projetos/@pulse-libs/core/package-lock.json b/projetos/@pulse-libs/core/package-lock.json index f1c14f7..d32d177 100644 --- a/projetos/@pulse-libs/core/package-lock.json +++ b/projetos/@pulse-libs/core/package-lock.json @@ -12,6 +12,7 @@ "zod": "^3.23.8" }, "devDependencies": { + "@testing-library/react": "^16.3.2", "@types/node": "^20.11.0", "@types/react": "^18.2.0", "@vitejs/plugin-react": "^4.2.0", @@ -325,6 +326,16 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", @@ -1488,6 +1499,98 @@ "dev": true, "license": "MIT" }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/dom/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@testing-library/dom/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1904,6 +2007,17 @@ "dev": true, "license": "MIT" }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -2236,6 +2350,17 @@ "node": ">=6" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -2246,6 +2371,14 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -2791,6 +2924,17 @@ "yallist": "^3.0.2" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -3229,6 +3373,31 @@ "node": ">=6" } }, + "node_modules/react": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "devOptional": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.6" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -3338,6 +3507,14 @@ "node": ">=v12.22.7" } }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "devOptional": true, + "license": "MIT", + "peer": true + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", diff --git a/projetos/@pulse-libs/core/package.json b/projetos/@pulse-libs/core/package.json index 6f76970..5f3cc4b 100644 --- a/projetos/@pulse-libs/core/package.json +++ b/projetos/@pulse-libs/core/package.json @@ -94,6 +94,7 @@ } }, "devDependencies": { + "@testing-library/react": "^16.3.2", "@types/node": "^20.11.0", "@types/react": "^18.2.0", "@vitejs/plugin-react": "^4.2.0", diff --git a/projetos/@pulse-libs/core/tests/hooks.test.ts b/projetos/@pulse-libs/core/tests/hooks.test.ts new file mode 100644 index 0000000..04a3e07 --- /dev/null +++ b/projetos/@pulse-libs/core/tests/hooks.test.ts @@ -0,0 +1,234 @@ +/** + * @pulse-libs/core — Hooks Tests (clean suite) + * @vitest-environment jsdom */ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { renderHook, act, waitFor } from '@testing-library/react'; +import { + useToggle, useAsync, useDebounce, useLocalStorage, + useMedia, useInterval, useClipboard, useFetch, +} from '../src/hooks/index'; + +const originalFetch = global.fetch; + +// ─ mocks globais ─────────────────────────────────────────────────── +const lsStore: Record = {}; +Object.defineProperty(global, 'localStorage', { + value: { + getItem: k => lsStore[k] ?? null, + setItem: (k,v) => { lsStore[k] = v; }, + removeItem: k => { delete lsStore[k]; }, + clear: () => { Object.keys(lsStore).forEach(k => delete lsStore[k]); }, + }, + writable: true, +}); +Object.defineProperty(global.navigator, 'clipboard', { + value: { writeText: vi.fn().mockResolvedValue(undefined) }, + configurable: true, writable: true, +}); +Object.defineProperty(global, 'matchMedia', { + value: vi.fn().mockReturnValue({ + matches: false, media: '', + addEventListener: vi.fn(), removeEventListener: vi.fn(), + addListener: vi.fn(), removeListener: vi.fn(), + }), + configurable: true, +}); + +beforeEach(() => { lsStore[''] = ''; Object.keys(lsStore).forEach(k => delete lsStore[k]); }); + +// ════════════════════════════════════════════ +describe('useToggle', () => { + it('inicia false por default', () => { + const { result } = renderHook(() => useToggle()); + expect(result.current[0]).toBe(false); + }); + it('inicia true se passado', () => { + const { result } = renderHook(() => useToggle(true)); + expect(result.current[0]).toBe(true); + }); + it('alterna com toggle()', () => { + const { result } = renderHook(() => useToggle(false)); + act(() => result.current[1]()); + expect(result.current[0]).toBe(true); + act(() => result.current[1]()); + expect(result.current[0]).toBe(false); + }); + it('força valor com set()', () => { + const { result } = renderHook(() => useToggle(false)); + act(() => result.current[2](true)); + expect(result.current[0]).toBe(true); + }); +}); + +// ════════════════════════════════════════════ +describe('useAsync', () => { + afterEach(() => { global.fetch = originalFetch; }); + + it('começa em idling e depois transiciona', async () => { + const fn = vi.fn(async () => Promise.resolve('ok')); + const { result } = renderHook(() => useAsync(fn, [])); + + // Imediatamente após renderHook: efeitos ainda não rodaram (microtask) + // Espera um ciclo de event loop + await waitFor(() => { + expect(['idle','loading','success','error']).toContain(result.current.status); + }); + // Com sucesso, deve estar em success + await waitFor(() => expect(result.current.status).toBe('success'), { timeout: 3000 }); + if ('data' in result.current) expect(result.current.data).toBe('ok'); + }); + + it('vai para error se promise rejeita', async () => { + const { result } = renderHook(() => useAsync(async () => { throw new Error('x'); }, [])); + + await waitFor(() => expect(result.current.status).toBe('error'), { timeout: 3000 }); + if ('error' in result.current) expect(result.current.error).toBeTruthy(); + }); + + it('re-executa só quando deps mudam', async () => { + const fn = vi.fn(async () => 'v'); + const { rerender } = renderHook( + ({ d }) => useAsync(fn, d), + { initialProps: { d: ['a'] as string[] } } + ); + await waitFor(() => expect(fn).toHaveBeenCalledTimes(1), { timeout: 3000 }); + rerender({ d: ['b'] as string[] }); + await waitFor(() => expect(fn).toHaveBeenCalledTimes(2), { timeout: 3000 }); + }); +}); + +// ════════════════════════════════════════════ +describe('useDebounce', () => { + it('retorna valor inicial', () => { + const { result } = renderHook(() => useDebounce('x', 200)); + expect(result.current).toBe('x'); + }); + + it('atrasa atualização até o timer', async () => { + const { result, rerender } = renderHook( + ({ v }) => useDebounce(v, 200), + { initialProps: { v: 'a' } } + ); + rerender({ v: 'b' }); + expect(result.current).toBe('a'); + await act(async () => { await new Promise(r => setTimeout(r, 300)); }); + expect(result.current).toBe('b'); + }); + + it('flushes última mudança em rápidas trocas', async () => { + const { result, rerender } = renderHook( + ({ v }) => useDebounce(v, 200), + { initialProps: { v: 1 } } + ); + rerender({ v: 2 }); rerender({ v: 3 }); rerender({ v: 4 }); + await act(async () => { await new Promise(r => setTimeout(r, 300)); }); + expect(result.current).toBe(4); + }); +}); + +// ════════════════════════════════════════════ +describe('useLocalStorage', () => { + it('retorna tupla [valor, setter] corretamente', () => { + const { result } = renderHook(() => + useLocalStorage('k1', 'hello' as string) as any + ); + expect(Array.isArray(result.current)).toBe(true); + expect(result.current[0]).toBe('hello'); + expect(typeof result.current[1]).toBe('function'); + }); + + it('salva e lê valor', () => { + const { result } = renderHook(() => + useLocalStorage('k2', 'init' as string) as any + ); + act(() => result.current[1]('novo')); + expect(result.current[0]).toBe('novo'); + }); + + it('persiste em sessões', () => { + lsStore['k3'] = '"saved"'; + const { result } = renderHook(() => + useLocalStorage('k3', 'def' as string) as any + ); + expect(result.current[0]).toBe('saved'); + }); + + it('aceita updater fn', () => { + const { result } = renderHook(() => + useLocalStorage('k4', 0 as number) as any + ); + act(() => result.current[1]((n: number) => n + 1)); + expect(result.current[0]).toBe(1); + }); +}); + +// ════════════════════════════════════════════ +describe('useMedia', () => { + it('retorna booleano', () => { + const { result } = renderHook(() => useMedia('screen')); + expect(typeof result.current).toBe('boolean'); + }); +}); + +// ════════════════════════════════════════════ +describe('useInterval', () => { + it('não executa quando ms é null', () => { + const fn = vi.fn(); + renderHook(() => useInterval(fn, null)); + expect(fn).not.toHaveBeenCalled(); + }); + + it('executa imediatamente com immediate=true', () => { + const fn = vi.fn(); + renderHook(() => useInterval(fn, 1000, true)); + expect(fn).toHaveBeenCalledTimes(1); + }); +}); + +// ════════════════════════════════════════════ +describe('useClipboard', () => { + it('inicia copied=false', () => { + const { result } = renderHook(() => useClipboard(2000)); + expect(result.current.copied).toBe(false); + }); + it('copia texto com sucesso', async () => { + const { result } = renderHook(() => useClipboard(0)); + const ok = await act(() => result.current.copy('hello')); + expect(ok).toBe(true); + expect(result.current.copied).toBe(true); + }); + it('retorna false em erro', async () => { + (global.navigator.clipboard.writeText as any).mockRejectedValue(new Error('denied')); + const { result } = renderHook(() => useClipboard(0)); + const ok = await act(() => result.current.copy('falha')); + expect(ok).toBe(false); + }); +}); + +// ════════════════════════════════════════════ +describe('useFetch', () => { + afterEach(() => { global.fetch = originalFetch; }); + + const r200 = (body: unknown) => + new Response(JSON.stringify(body), { status: 200, headers: { 'Content-Type': 'application/json' } }); + + it('retorna success para HTTP 200', async () => { + global.fetch = vi.fn(() => Promise.resolve(r200({ ok: true }))) as any; + const { result } = renderHook(() => useFetch('/api', {}) as any); + await waitFor(() => expect(result.current.status).toBe('success'), { timeout: 4000 }); + expect((result.current as any).data.ok).toBe(true); + }); + + it('retorna error para HTTP 404', async () => { + global.fetch = vi.fn(() => Promise.resolve( + new Response('nf', { status: 404, headers: { 'Content-Type': 'application/json' } }))) as any; + const { result } = renderHook(() => useFetch('/404', {}) as any); + await waitFor(() => expect(result.current.status).toBe('error'), { timeout: 4000 }); + }); + + it('retorna error em falha de rede', async () => { + global.fetch = vi.fn(() => Promise.reject(new TypeError('net'))) as any; + const { result } = renderHook(() => useFetch('/fail', {}) as any); + await waitFor(() => expect(result.current.status).toBe('error'), { timeout: 4000 }); + }); +}); diff --git a/skills/agent-browser-clawdbot/.clawhub/origin.json b/skills/agent-browser-clawdbot/.clawhub/origin.json index d7d92d3..9b898e1 100644 --- a/skills/agent-browser-clawdbot/.clawhub/origin.json +++ b/skills/agent-browser-clawdbot/.clawhub/origin.json @@ -3,5 +3,5 @@ "registry": "https://clawhub.ai", "slug": "agent-browser-clawdbot", "installedVersion": "0.1.0", - "installedAt": 1779241639613 + "installedAt": 1779243267499 } diff --git a/skills/multi-search-engine-2-0-1/.clawhub/origin.json b/skills/multi-search-engine-2-0-1/.clawhub/origin.json index 658ccf4..272e27b 100644 --- a/skills/multi-search-engine-2-0-1/.clawhub/origin.json +++ b/skills/multi-search-engine-2-0-1/.clawhub/origin.json @@ -3,5 +3,5 @@ "registry": "https://clawhub.ai", "slug": "multi-search-engine-2-0-1", "installedVersion": "1.0.0", - "installedAt": 1779241678486 + "installedAt": 1779243337647 } diff --git a/skills/redis-labs-integration/.clawhub/origin.json b/skills/redis-labs-integration/.clawhub/origin.json index da5a90f..3f50685 100644 --- a/skills/redis-labs-integration/.clawhub/origin.json +++ b/skills/redis-labs-integration/.clawhub/origin.json @@ -3,5 +3,5 @@ "registry": "https://clawhub.ai", "slug": "redis-labs-integration", "installedVersion": "1.0.2", - "installedAt": 1779241689038 + "installedAt": 1779243351883 } diff --git a/skills/self-improvement/.clawhub/origin.json b/skills/self-improvement/.clawhub/origin.json index 52103f5..14bab10 100644 --- a/skills/self-improvement/.clawhub/origin.json +++ b/skills/self-improvement/.clawhub/origin.json @@ -3,5 +3,5 @@ "registry": "https://clawhub.ai", "slug": "self-improvement", "installedVersion": "1.0.0", - "installedAt": 1779241666977 + "installedAt": 1779243314748 } diff --git a/skills/vision/.clawhub/origin.json b/skills/vision/.clawhub/origin.json index 1aa04d3..65dc867 100644 --- a/skills/vision/.clawhub/origin.json +++ b/skills/vision/.clawhub/origin.json @@ -3,5 +3,5 @@ "registry": "https://clawhub.ai", "slug": "vision", "installedVersion": "3.5.0", - "installedAt": 1779241651452 + "installedAt": 1779243291577 }