--- name: spoke-dev description: Desenvolvimento de Spokes do AcidaOS em Next.js/TypeScript — criar aplicações Spoke, componentes React com testes e Storybook. Usar quando "spoke", "dashboard acidaos", "next.js acidaos", "componente react acidaos", "ts-create-spoke", "acidaos ui", "acidaos frontend". allowed-tools: Read, Write, Edit, Bash, mcp__memory-supabase__search_memories, mcp__gitea__get_file_content, mcp__gitea__create_file, mcp__gitea__update_file --- # AcidaOS Spoke Dev Skill para desenvolvimento dos **Spokes do AcidaOS** em Next.js/TypeScript. ## Contexto do Projecto Os Spokes são as aplicações de interface que comunicam com o Core via API interna: ``` acidaos-dashboard (Next.js 15 + App Router) ├── src/ │ ├── app/ ← App Router pages │ ├── components/ ← Componentes React │ │ ├── ui/ ← Componentes base (shadcn/ui) │ │ └── features/ ← Componentes de funcionalidade │ ├── lib/ │ │ ├── api/ ← Client para AcidaOS Core API │ │ └── hooks/ ← React hooks custom │ └── types/ ← TypeScript types partilhados ├── __tests__/ ← Jest + Testing Library └── .storybook/ ← Storybook ``` **Stack:** Next.js 15 | TypeScript 5 | React 19 | Tailwind CSS | shadcn/ui | Vercel AI SDK | Vitest | Playwright ## Protocolo Inicial ``` mcp__memory-supabase__search_memories "acidaos spoke dashboard [componente]" mcp__gitea__get_file_content acidaos-dashboard/package.json ``` --- ## Operações ### 1. Criar nova aplicação Spoke (`/ts-create-spoke-app`) **Input:** Nome do Spoke e propósito **Estrutura a criar:** ```bash # No container dev (Regra #48) cd /root/Dev npx create-next-app@latest acidaos- \ --typescript \ --tailwind \ --app \ --src-dir \ --import-alias "@/*" \ --no-git ``` **Ficheiros obrigatórios após scaffold:** `src/lib/api/core-client.ts` — cliente para o Core: ```typescript /** * AcidaOS Core API Client * * @author Descomplicar® Crescimento Digital * @link https://descomplicar.pt */ const CORE_API_URL = process.env.ACIDAOS_CORE_URL ?? 'http://localhost:3001'; export async function coreRequest( endpoint: string, options?: RequestInit ): Promise { const res = await fetch(`${CORE_API_URL}${endpoint}`, { ...options, headers: { 'Content-Type': 'application/json', 'X-AcidaOS-Version': '1', ...options?.headers, }, }); if (!res.ok) { throw new Error(`Core API error: ${res.status} ${res.statusText}`); } return res.json() as Promise; } ``` `src/types/index.ts` — tipos base: ```typescript export interface AgentTask { id: string; status: 'pending' | 'running' | 'completed' | 'failed'; createdAt: string; completedAt?: string; } export interface CoreHealth { status: 'healthy' | 'degraded' | 'down'; version: string; uptime: number; } ``` --- ### 2. Criar componente React (`/ts-create-react-component`) **Input:** Descrição do componente (nome, props, comportamento) **Template base — componente:** `src/components/features//.tsx`: ```typescript /** * * * @author Descomplicar® Crescimento Digital */ import { type FC } from 'react'; import { cn } from '@/lib/utils'; interface Props { // props do componente className?: string; } export const : FC<Props> = ({ className }) => { return (
{/* conteúdo */}
); }; .displayName = ''; ``` **Template — teste (Vitest + Testing Library):** `src/components/features//.test.tsx`: ```typescript import { render, screen } from '@testing-library/react'; import { describe, it, expect } from 'vitest'; import { } from './'; describe('', () => { it('renderiza sem erros', () => { render(< />); // asserção específica ao componente }); it('aceita className personalizada', () => { const { container } = render(< className="test-class" />); expect(container.firstChild).toHaveClass('test-class'); }); }); ``` **Template — Storybook:** `src/components/features//.stories.tsx`: ```typescript import type { Meta, StoryObj } from '@storybook/react'; import { } from './'; const meta: Meta> = { title: 'Features/', component: , parameters: { layout: 'centered', }, }; export default meta; type Story = StoryObj>; export const Default: Story = { args: {}, }; ``` **Criar index de exportação:** `src/components/features//index.ts`: ```typescript export { } from './'; export type { Props } from './'; ``` --- ## Padrões Obrigatórios ### TypeScript - `strict: true` em `tsconfig.json` — sem `any` implícito - Props sempre tipadas com interface exportada - Hooks custom em `src/lib/hooks/use.ts` - Server Components por defeito; `'use client'` apenas quando necessário ### Performance - `next/image` para todas as imagens - `loading="lazy"` em componentes pesados - `Suspense` boundaries em torno de data-fetching - Cache de API calls com `unstable_cache` ou React cache ### Segurança - Nunca expor `ACIDAOS_CORE_URL` no cliente (apenas server-side) - Sanitizar input do utilizador antes de enviar ao Core - Usar Next.js Server Actions para mutações --- ## Quality Gate ```bash pnpm build # zero erros TypeScript pnpm test # todos os testes passam pnpm lint # zero erros ESLint ``` ## Checklist Entrega - [ ] Tipos TypeScript sem `any` - [ ] Testes criados (>80% cobertura) - [ ] Story Storybook criada - [ ] `displayName` definido no componente - [ ] `pnpm build` sem erros - [ ] Client/Server components correctamente marcados - [ ] CHANGELOG.md actualizado --- **Versão**: 1.0.0 | **Autor**: Descomplicar® | **Plugin**: acidaos --- ## Healing Log Registo de erros conhecidos e como evitá-los. Lido automaticamente antes de executar. ```jsonl {"date":"","issue":"","fix":"","source":"user|auto"} ``` *Adicionar nova linha após cada erro corrigido.*