---
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-<nome> \
  --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<T>(
  endpoint: string,
  options?: RequestInit
): Promise<T> {
  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<T>;
}
```

`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/<Nome>/<Nome>.tsx`:
```typescript
/**
 * <Nome> — <Descrição curta>
 *
 * @author Descomplicar® Crescimento Digital
 */

import { type FC } from 'react';
import { cn } from '@/lib/utils';

interface <Nome>Props {
  // props do componente
  className?: string;
}

export const <Nome>: FC<<Nome>Props> = ({ className }) => {
  return (
    <div className={cn('', className)}>
      {/* conteúdo */}
    </div>
  );
};

<Nome>.displayName = '<Nome>';
```

**Template — teste (Vitest + Testing Library):**

`src/components/features/<Nome>/<Nome>.test.tsx`:
```typescript
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { <Nome> } from './<Nome>';

describe('<Nome>', () => {
  it('renderiza sem erros', () => {
    render(<<Nome> />);
    // asserção específica ao componente
  });

  it('aceita className personalizada', () => {
    const { container } = render(<<Nome> className="test-class" />);
    expect(container.firstChild).toHaveClass('test-class');
  });
});
```

**Template — Storybook:**

`src/components/features/<Nome>/<Nome>.stories.tsx`:
```typescript
import type { Meta, StoryObj } from '@storybook/react';
import { <Nome> } from './<Nome>';

const meta: Meta<typeof <Nome>> = {
  title: 'Features/<Nome>',
  component: <Nome>,
  parameters: {
    layout: 'centered',
  },
};

export default meta;
type Story = StoryObj<typeof <Nome>>;

export const Default: Story = {
  args: {},
};
```

**Criar index de exportação:**

`src/components/features/<Nome>/index.ts`:
```typescript
export { <Nome> } from './<Nome>';
export type { <Nome>Props } from './<Nome>';
```

---

## 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<Nome>.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.*
