Design and generate a complete frontend UI for your Fyso app. Handles discovery (objective, roles, style), mockup validation, API contracts documentation, and code generation using @fyso/ui components.
You design and generate a complete frontend for a Fyso app. This is the final phase of the GSD pipeline — after entities, rules, and channels are built, this skill creates the UI that users interact with.
The generated UI uses Astro, React, and Tailwind CSS — calling the Fyso API directly without UI library dependencies.
The UI pipeline has a strict order. Never skip steps or build the frontend before the backend is ready.
BACKEND FIRST (via /fyso:plan + /fyso:build)
└─▶ Phase 1..N: entities, rules, channels — all published in tenant
UI DESIGN (this skill — design only, no code yet)
├─▶ /fyso:ui plan → discovery questions → UI-SPEC.md
│ or
│ /fyso:ui infer → AI infers everything → UI-SPEC.md + MOCKUPS + CONTRACTS
├─▶ /fyso:ui mockup → ASCII wireframes → UI-MOCKUPS.md (approved)
└─▶ /fyso:ui contracts → API contracts, roles, auth → UI-CONTRACTS.md
UI BUILD (only when backend is fully built)
└─▶ /fyso:ui build → generate frontend code
UI AUDIT (after build)
└─▶ /fyso:ui audit → security, domain, permissions, UX checks
Gate rule: /fyso:ui build is BLOCKED unless:
If either condition fails, explain what's missing and suggest the correct next step instead.
/fyso-ui plan # Discovery + design: ask questions, generate UI spec
/fyso-ui infer "<desc>" # AI-inferred: generate everything from short description
/fyso-ui mockup # Generate ASCII mockups for validation
/fyso-ui contracts # Document API contracts, roles, auth flows
/fyso-ui build # Generate the actual UI code (blocked until backend ready)
/fyso-ui audit # Audit: security, domain, permissions, UX problems
/fyso-ui all # Full pipeline: plan → mockup → contracts → build
Before asking anything, load context:
.planning/PROJECT.md — app domain, core value.planning/REQUIREMENTS.md — entities, rules, relations.planning/STATE.md — current entities, what's publishedselect_tenant → list_entities → get_entity_schema (for each)
This gives you the complete data model to design the UI around.
Ask the user systematically. Group questions into rounds of 3-5 max.
What's the main objective of this UI?
Who are the users? Map to roles:
owner — Full control, sees everythingadmin — Manages users and configurationmember — Creates and edits records (employees, staff)viewer — Read-only access (clients, external users)public — No login required (landing page, catalog)Does each role see a different UI?
For each entity, what operations does the UI expose?
| Entity | Public View | List | Create | Edit | Delete | Detail | |--------|-------------|------|--------|------|--------|--------| | pacientes | no | yes | yes | yes | no | yes | | sesiones | no | yes | yes | yes | no | yes | | facturas | no | yes | no | no | no | yes |
Any special views?
Search and filters?
Authentication flow:
API access model:
User management in UI?
Visual style:
Layout pattern:
Responsive?
Language:
Write .planning/UI-SPEC.md:
# UI Specification
## Objective
{What the UI does and for whom}
## Users & Roles
| Role | Access | Description |
|------|--------|-------------|
| owner | Full | App owner, full CRUD + user management |
| admin | Management | Manages records and users |
| member | CRUD | Creates and edits data |
| viewer | Read-only | Views data, no modifications |
| public | Public pages | No login, sees public content |
## Pages
### Public Pages (no auth)
| Page | Route | Description |
|------|-------|-------------|
| Landing | `/` | App landing page |
| Login | `/login` | Login form |
| Register | `/register` | Self-registration (if enabled) |
### Authenticated Pages
| Page | Route | Roles | Description |
|------|-------|-------|-------------|
| Dashboard | `/app` | all | KPIs and quick actions |
| Pacientes | `/app/pacientes` | owner, admin, member | List + CRUD |
| Paciente Detail | `/app/pacientes/:id` | owner, admin, member | Detail + related records |
| Sesiones | `/app/sesiones` | all | List + CRUD |
| Facturas | `/app/facturas` | owner, admin | List + view only |
| Users | `/app/users` | owner, admin | User management |
| Profile | `/app/profile` | all | Own profile |
## Entity → UI Mapping
| Entity | List | Create | Edit | Delete | Detail | Roles |
|--------|------|--------|------|--------|--------|-------|
| pacientes | DataGrid | DynamicForm | DynamicForm | confirm | RecordDetail | owner, admin, member |
| sesiones | DataGrid | DynamicForm | DynamicForm | no | RecordDetail | all |
| facturas | DataGrid | no | no | no | RecordDetail | owner, admin |
## Authentication
- **Method:** Tenant user tokens via `/api/auth/tenant/login`
- **Self-registration:** {yes/no}
- **Session storage:** httpOnly cookie / localStorage
- **Token refresh:** automatic on 401
## Style
- **Theme:** {minimal/professional/modern/dark}
- **Layout:** {sidebar/topnav/landing+app}
- **Colors:** {primary, accent, background}
- **Responsive:** {yes/no}
- **Language:** {es/en/both}
## Dashboard KPIs
| KPI | Source | Query |
|-----|--------|-------|
| Total pacientes | query_records(pacientes, limit:1) | pagination.total |
| Sesiones este mes | query_records(sesiones, filters:{fecha>=month_start}) | count |
| Facturacion total | query_records(facturas, filters:{estado=pagada}) | sum(total) |
## Navigation Structure
{Describe the sidebar/navbar items and their hierarchy}
Fast-track mode. The user provides a short description and the AI infers everything: objective, roles, auth, pages, CRUD permissions, style, layout, KPIs, and navigation. No questions asked. The AI uses domain knowledge + the actual tenant data model to make smart defaults.
/fyso-ui infer "Panel admin para consultorio de fonoaudiologia"
/fyso-ui infer "Portal de clientes para estudio contable"
/fyso-ui infer "Catalogo publico de productos con area de admin"
Read ALL available context:
.planning/PROJECT.md — app domain, core value.planning/REQUIREMENTS.md — entities, rules, relations.planning/STATE.md — current entities, what's publishedselect_tenant → list_entities → get_entity_schema (for each)
list_business_rules (for each entity)
From the entity names, field types, and relations, infer:
| Signal | Inference |
|--------|-----------|
| Entity names are Spanish | Language = Spanish |
| Entities like "pacientes", "sesiones", "profesionales" | Medical/therapy domain |
| Entities like "productos", "pedidos", "clientes" | E-commerce/retail domain |
| Entities like "alumnos", "materias", "notas" | Education domain |
| Entities like "empleados", "asistencia", "nomina" | HR domain |
| Entity with estado field (status) | Kanban or status filters |
| Entity with fecha field (date) | Calendar view candidate |
| Entity with monto/total/precio | Financial KPI candidate |
| Entity A has relation → Entity B | B is parent, A is detail. B detail page shows A list |
| Entity has activo boolean | Active/inactive filter |
| Presence of facturas/pagos | Revenue dashboard KPI |
| No usuarios entity | Auth via Fyso tenant users (built-in) |
Use a decision matrix based on the description + domain:
| Decision | Internal Admin Panel | Client Portal | Public Site | Mixed | |----------|---------------------|---------------|-------------|-------| | Keyword triggers | "panel", "admin", "gestion", "interno" | "portal", "clientes", "mis datos" | "catalogo", "publico", "sitio" | "portal + admin", "publico + gestion" | | Layout | Sidebar | Sidebar or TopNav | Landing + App | Landing + App (with sidebar for admin) | | Auth | Login only, no self-reg | Login + self-registration | No auth (public) or optional login | Mixed | | Roles | owner, admin, member | owner, admin, viewer (client) | public (+ admin internally) | owner, admin, member, viewer, public | | Style | Professional | Modern | Modern/Minimal | Modern | | Responsive | Desktop + mobile | Desktop + mobile | Mobile-first | Desktop + mobile | | User mgmt | Admin creates users | Self-register + admin manages | No users | Both |
CRUD inference per entity:
| Entity Type | Default CRUD |
|-------------|-------------|
| Parent entity (patients, clients, products) | List, Create, Edit, Detail. Delete only for owner/admin |
| Detail entity (sessions, orders, invoices) | List, Create, Edit, Detail. Created from parent detail page |
| Reference entity (categories, statuses, types) | List, Create, Edit. Admin only |
| Read-only entity (logs, audit trails) | List, Detail only |
| Entities with total/monto | No create/edit from UI if calculated by rules |
Dashboard KPI inference:
| Domain Signal | KPI |
|---------------|-----|
| Count of parent entities | "Total {entity}: {count}" |
| Entity with date field | "{entity} este mes: {count}" |
| Entity with money field | "Facturacion: ${sum}" |
| Entity with status field | "{count} pendientes / {count} completados" |
| Entity with boolean activo | "{count} activos" |
Generate in sequence, without stopping to ask:
UI-SPEC.md — Complete spec with all inferred decisionsUI-MOCKUPS.md — ASCII wireframes for every pageUI-CONTRACTS.md — Full API contractsAfter generating all 3 documents, present a concise summary:
## La IA infirió lo siguiente:
**Tipo de app:** Panel admin interno
**Dominio:** Consultorio de fonoaudiología
**Idioma:** Español
**Roles:**
- owner: Todo (CRUD + usuarios + config)
- admin: Gestión (CRUD + usuarios)
- member: Operativo (CRUD de pacientes y sesiones)
**Páginas (7):**
- Dashboard: 3 KPIs (pacientes activos, sesiones este mes, facturado)
- Pacientes: Lista + crear + editar + detalle (con sesiones y facturas)
- Sesiones: Lista + crear + editar + detalle
- Facturas: Lista + detalle (solo lectura, generadas por regla)
- Profesionales: Lista + crear + editar
- Usuarios: Gestión (solo admin+)
- Perfil: Datos propios
**Auth:** Login con usuario de tenant, sin auto-registro
**Layout:** Sidebar, responsive
**Estilo:** Professional
¿Quieres que ajuste algo antes de generar el código?
The user can:
After the user approves the UI design, check whether the backend is ready:
select_tenant → list_entities
Compare the published entities against the entities listed in UI-SPEC.md.
If all entities are published:
✅ UI diseñada y backend listo.
Próximo paso:
/fyso:ui build — generar el frontend
If entities are missing or not published:
⏳ UI diseñada. Falta construir el backend primero.
Entidades requeridas por la UI: Publicadas en tenant:
- pacientes ✅ pacientes
- sesiones ❌ sesiones (no existe)
- facturas ❌ facturas (no existe)
Construí el backend primero:
/fyso:build phase 2 → sesiones + facturas
Cuando termines, volvé con:
/fyso:ui build
Never suggest /fyso:ui build if the backend is not ready.
sesiones belongs to pacientes, the paciente detail page shows sesiones. Don't create a separate sesiones page unless it makes sense to see all sessions across patients.factura.total, don't let users edit that field in the form.Before writing code, validate the layout with the user via ASCII mockups. This prevents expensive rework.
.planning/UI-SPEC.md.planning/UI-MOCKUPS.mdUse ASCII art that clearly shows layout, navigation, and content areas:
┌─────────────────────────────────────────────────────────────┐
│ Logo Mi Consultorio [Juan] [Logout] │
├──────────┬──────────────────────────────────────────────────┤
│ │ │
│ Dashboard│ Dashboard │
│ ─────────│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ Pacientes│ │ 127 │ │ 43 │ │ $450,000 │ │
│ Sesiones │ │ Pacientes│ │ Sesiones │ │ Facturado│ │
│ Facturas │ │ activos │ │ este mes │ │ este mes │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │
│ ─────────│ │
│ Usuarios │ Proximas sesiones │
│ Perfil │ ┌───────────────────────────────────────────┐ │
│ │ │ Fecha │ Paciente │ Prof. │ Estado │ │
│ │ │ 25/02 │ Maria Garcia │ Lic.Ana │ Confirm │ │
│ │ │ 25/02 │ Pedro Lopez │ Lic.Ana │ Pend. │ │
│ │ │ 26/02 │ Laura Diaz │ Lic.Ana │ Prog. │ │
│ │ └───────────────────────────────────────────┘ │
│ │ │
└──────────┴──────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Logo Mi Consultorio [Juan] [Logout] │
├──────────┬──────────────────────────────────────────────────┤
│ │ │
│ Dashboard│ Pacientes [+ Nuevo Paciente] │
│ ─────────│ ┌─────────────────────────────────────────────┐│
│ Pacientes│ │ [Buscar...] [Filtro: Activos ▾] ││
│ > Lista │ ├─────────────────────────────────────────────┤│
│ Sesiones │ │ Nombre │ DNI │ O.Social │ Tel ││
│ Facturas │ │──────────────│──────────│──────────│────────││
│ │ │ Maria Garcia │ 30123456 │ OSDE │ 11-... ││
│ ─────────│ │ Pedro Lopez │ 28456789 │ Swiss │ 11-... ││
│ Usuarios │ │ Laura Diaz │ 35789012 │ Galeno │ 11-... ││
│ Perfil │ ├─────────────────────────────────────────────┤│
│ │ │ Mostrando 1-10 de 127 [< Prev] [Next >] ││
│ │ └─────────────────────────────────────────────┘│
│ │ │
└──────────┴──────────────────────────────────────────────────┘
Generate mockups for:
Document everything needed to connect the UI to the backend: API endpoints, auth flows, data shapes, and role permissions.
.planning/UI-SPEC.md and .planning/REQUIREMENTS.md.planning/UI-CONTRACTS.mdWrite .planning/UI-CONTRACTS.md:
# UI → API Contracts
## Base Configuration
| Key | Value |
|-----|-------|
| API URL | `https://{host}/api` |
| Tenant ID | `{tenant-slug}` |
| Auth mode | Tenant user tokens |
| Token header | `X-API-Key: {token}` + `X-Tenant-ID: {slug}` |
## Authentication Endpoints
> All responses are wrapped in `{ success, data }` — `Response:` below shows the **unwrapped** `data` payload.
### Login
POST /api/auth/tenant/login Headers: X-Tenant-ID: {slug} Body: { email, password } Response data: { token, user: { id, email, name, role } }
### Register (if self-registration)
POST /api/auth/tenant/register Headers: X-Tenant-ID: {slug} Body: { email, password, name } Response data: { token, user: { id, email, name, role } }
### Logout
POST /api/auth/tenant/logout Headers: X-API-Key: {token}, X-Tenant-ID: {slug}
### Current User
GET /api/auth/tenant/me Headers: X-API-Key: {token}, X-Tenant-ID: {slug} Response data: { id, email, name, role, permissions }
## Data Endpoints
### {Entity Name}
**Schema:**
| Field | Type | Required | Notes |
|-------|------|----------|-------|
| nombre | text | yes | — |
| email | email | no | — |
| ...
**List:**
GET /api/entities/{entity}/records?page=1&limit=20&sort=name&order=asc&resolve_depth=1 Headers: X-API-Key: {token}, X-Tenant-ID: {slug} Response data: { items: Record[], total, page, limit, totalPages }
Read records from `data.items` — NOT `data.data`. Pass `resolve_depth=1` for entities with relations so related fields become nested objects (with names) instead of UUID strings.
**Get One:**
GET /api/entities/{entity}/records/{id} Response data: { id, entityId, ...fields, createdAt, updatedAt }
Records are flat (since v1.26.0): read fields as `record.fieldKey`, never `record.data.fieldKey`. Pass `?resolve_depth=1` (max 3) when you need related fields expanded as nested objects instead of UUID strings.
**Create:**
POST /api/entities/{entity}/records Body: { field1: value1, field2: value2 } Response data: { id, ...fields }
**Update:**
PUT /api/entities/{entity}/records/{id} Body: { field1: newValue } Response data: { id, ...fields }
**Delete:**
DELETE /api/entities/{entity}/records/{id} Response: { success: true }
**Filters:** the REST API supports AND-only compound filters via `?filters=field1 = value AND field2 > N`. For OR conditions, fetch the AND subset and filter `data.items` client-side.
## Role → Permission Matrix
| Action | owner | admin | member | viewer | public |
|--------|-------|-------|--------|--------|--------|
| View dashboard | yes | yes | yes | yes | no |
| List pacientes | yes | yes | yes | yes | no |
| Create paciente | yes | yes | yes | no | no |
| Edit paciente | yes | yes | yes | no | no |
| Delete paciente | yes | yes | no | no | no |
| Manage users | yes | yes | no | no | no |
| View own profile | yes | yes | yes | yes | no |
## API Keys & Tokens
| Type | Scope | TTL | Storage |
|------|-------|-----|---------|
| Admin API key | All tenants, all operations | Permanent | Server env only |
| User session token | Single tenant, role-based | 7 days | httpOnly cookie |
| OAuth access token | MCP operations | 1 hour | Memory |
## User Levels
| Level | How created | Permissions |
|-------|-------------|-------------|
| Owner | First admin or platform admin | Everything |
| Admin | Created by owner | Manage users + all CRUD |
| Member | Created by admin or self-register | CRUD on assigned entities |
| Viewer | Created by admin or self-register | Read-only |
## Error Handling
| Code | HTTP | UI Action |
|------|------|-----------|
| UNAUTHORIZED | 401 | Redirect to /login |
| FORBIDDEN | 403 | Show "no permission" message |
| NOT_FOUND | 404 | Show "not found" page |
| VALIDATION_ERROR | 400 | Show field-level errors on form |
| BUSINESS_RULE_ERROR | 400 | Show rule error message as toast |
Before writing a single line of frontend code, verify:
1. Design docs exist:
.planning/UI-SPEC.md → if missing: run /fyso:ui plan or /fyso:ui infer first.planning/UI-MOCKUPS.md → if missing: run /fyso:ui mockup first.planning/UI-CONTRACTS.md → if missing: run /fyso:ui contracts first2. Backend is built — query the tenant:
select_tenant → list_entities
Cross-reference with every entity listed in UI-SPEC.md. If any entity is missing or not published, STOP and tell the user:
🚫 No se puede construir el frontend todavía.
Entidades faltantes en el tenant:
- {entity_name} (requerida por UI-SPEC.md, no publicada)
<!-- Content truncated for initial SEO render. Open the source file tab for the full file. -->
Category:other