arckode-ui 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/skills/SKILL.md +866 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arckode-ui",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "Frontend framework con .ark SFCs, signals, file-system router y analyzer estático con sugerencias concretas de fix. Diseñado para máxima predictibilidad de output de IA.",
6
6
  "keywords": [
@@ -38,7 +38,7 @@
38
38
  "main": "./dist/index.js",
39
39
  "types": "./dist/index.d.ts",
40
40
  "bin": {
41
- "ark": "./dist/cli.js"
41
+ "ark": "dist/cli.js"
42
42
  },
43
43
  "files": [
44
44
  "dist",
@@ -0,0 +1,866 @@
1
+ # SKILL: Arckode UI — AI Development Protocol
2
+
3
+ > Leé esto COMPLETO antes de escribir una sola línea de `.ark` o de código que use `arckode-ui`.
4
+ > Este skill se activa cuando: hay archivos `.ark`, se importa de `arckode-ui` o `arckode-ui/vite`,
5
+ > se usa `defineComponent`, `signal`, `computed`, `defineStore`, `createService`, `ark analyze`, `ark generate`.
6
+
7
+ ## SUB-SKILLS DISPONIBLES (publicados con el paquete npm)
8
+
9
+ Cuando necesités detalle profundo de un área, leer el sub-skill correspondiente.
10
+ Los sub-skills están en `./node_modules/arckode-ui/skills/` (si el proyecto usa npm/bun install):
11
+
12
+ | Contexto | Sub-skill a leer |
13
+ |----------|------------------|
14
+ | Escribir un `.ark` para usuario, patrones de UI (modal con slot, form con validación, lista con curried action, `:class` reactivo) | `./node_modules/arckode-ui/skills/components/SKILL.md` |
15
+ | Signals, computed, watch, effect, lifecycle, renderer, comunicación padre↔hijo | `./node_modules/arckode-ui/skills/runtime/SKILL.md` |
16
+ | Parser, template-compiler, vite-plugin, agregar directiva nueva | `./node_modules/arckode-ui/skills/compiler/SKILL.md` |
17
+ | Agregar regla nueva al analyzer, fix suggestions, mensajes | `./node_modules/arckode-ui/skills/analyzer/SKILL.md` |
18
+ | CLI `ark`, plantillas del generator, build del bin | `./node_modules/arckode-ui/skills/cli/SKILL.md` |
19
+ | Vitest + happy-dom, mockear fetch, testar componentes | `./node_modules/arckode-ui/skills/testing/SKILL.md` |
20
+
21
+ > Si el proyecto está desarrollando el framework (no usándolo como dep), los sub-skills están en `./skills/` directamente.
22
+
23
+ ---
24
+
25
+ ## 1. QUÉ ES ARCKODE UI (entiendelo ANTES de codear)
26
+
27
+ Framework frontend **architected FOR AI** — su objetivo no es ser flexible sino **predecible**.
28
+ Un componente `.ark` SIEMPRE tiene la misma estructura. El analizador detecta cuando la rompés
29
+ y SIEMPRE sugiere el fix concreto en el campo `violation.fix`.
30
+
31
+ **Pila obligatoria:**
32
+ - TypeScript strict (no `any`)
33
+ - Vite con `arkcodeUi()` plugin
34
+ - Signals (no ref/reactive)
35
+ - HTML declarativo compilado (no JSX)
36
+ - Bun (recomendado) o Node ≥ 18
37
+
38
+ **El composition root es `main.ts` + el `App.ark` root.** Si un componente no está importado en alguna chain desde App, no existe.
39
+
40
+ ---
41
+
42
+ ## 2. PROTOCOLO OBLIGATORIO (antes de escribir código)
43
+
44
+ ```
45
+ PASO 1 — IDENTIFICAR ALCANCE
46
+ ¿La tarea toca 1 componente nuevo? → Seguir
47
+ ¿La tarea toca 1 componente existente? → Leer el .ark primero, no asumir
48
+ ¿La tarea toca varios componentes hermanos? → Identificar si necesita un store
49
+ ¿La tarea toca el routing? → Trabajar en src/pages/
50
+ ¿La tarea toca HTTP a un backend? → Crear/modificar service, NO tocar componente
51
+ ¿La tarea toca estado global compartido? → defineStore en src/stores/
52
+ ¿La tarea toca el framework en sí? → DETENERSE. Ese es trabajo del repo del framework.
53
+
54
+ PASO 2 — VERIFICAR REGLAS (ver §3)
55
+ ¿Estoy poniendo lógica en template? → REGLA #11 — extraer a computed/action
56
+ ¿Estoy usando ref()/reactive()? → REGLA #8 — usar signal()
57
+ ¿Estoy haciendo fetch() en el componente? → REGLA #10 — mover a service
58
+ ¿Estoy usando provide/inject/v-model? → NO EXISTEN. Reglas #7 y #9
59
+ ¿Mi handler @click no empieza con actions.? → REGLA #4
60
+ ¿Mi v-for tiene colección sin namespace? → REGLA #5
61
+ ¿Mi return de setup tiene llaves extra? → REGLA #3
62
+ ¿Mi prop no tiene type? → REGLA #1
63
+ ¿Mi emit usa camelCase? → REGLA #2
64
+
65
+ PASO 3 — GENERAR
66
+ Si es componente nuevo: copiar la PLANTILLA CANÓNICA (§5) y adaptar
67
+ Si es modificación: respetar el orden del setup() (§4)
68
+ Si es estado global: copiar PATRÓN DE STORE (§7)
69
+ Si es service HTTP: copiar PATRÓN DE SERVICE (§8)
70
+
71
+ PASO 4 — VERIFICAR (NO ASUMIR que funciona)
72
+ npx ark analyze → 0 errors. Si hay errors, leer violation.fix y aplicar
73
+ bun test → si trabajás en el framework
74
+ Probar en navegador → npx vite (o bun dev) y verificar que la feature funciona
75
+ ```
76
+
77
+ ---
78
+
79
+ ## 3. REGLAS INMUTABLES (no negociables — el analyzer las detecta)
80
+
81
+ Cada regla tiene un código que aparece en el output del analyzer. La IA puede referenciarlas
82
+ diciendo "esto viola REGLA #N" para ser explícita.
83
+
84
+ ### ⚠ REGLA #1 — Toda prop DEBE tener `type` (clase nativa)
85
+
86
+ ```typescript
87
+ // ❌
88
+ props: { name: { required: true } } // PROP_MISSING_TYPE
89
+ props: { name: { type: 'string' } } // string en vez de String
90
+
91
+ // ✅
92
+ props: { name: { type: String, required: true } }
93
+ ```
94
+
95
+ Tipos válidos: `String | Number | Boolean | Array | Object | Function` (clases nativas).
96
+
97
+ ### ⚠ REGLA #2 — `emits` SIEMPRE en kebab-case
98
+
99
+ ```typescript
100
+ // ❌
101
+ emits: ['userSaved', 'formCancelled'] // EMIT_CAMELCASE
102
+
103
+ // ✅
104
+ emits: ['user-saved', 'form-cancelled']
105
+ ```
106
+
107
+ Al escuchar: `<Child @user-saved="actions.x">`. Al emitir: `emit('user-saved', payload)`.
108
+
109
+ ### ⚠ REGLA #3 — `setup()` retorna SOLO `state`, `computed`, `actions`
110
+
111
+ ```typescript
112
+ // ❌
113
+ return { state: {}, computed: {}, actions: {}, methods: {} } // SETUP_UNKNOWN_RETURN_KEY
114
+ return { signals: {}, ... } // signals no es válido
115
+
116
+ // ✅
117
+ return { state, computed, actions }
118
+ ```
119
+
120
+ Llaves OK: `state` (signals mutables), `computed` (derivados readonly), `actions` (functions).
121
+ NO se permite otra. Si tu return tiene una llave extra, mover su contenido a una de las 3.
122
+
123
+ ### ⚠ REGLA #4 — Handlers de eventos SIEMPRE `actions.xxx`
124
+
125
+ ```html
126
+ <!-- ❌ -->
127
+ <button @click="handleClick"> <!-- HANDLER_NOT_IN_ACTIONS -->
128
+ <button @click="state.count.value++"> <!-- LOGIC_IN_TEMPLATE + HANDLER_NOT_IN_ACTIONS -->
129
+ <button @click="submit()">
130
+
131
+ <!-- ✅ -->
132
+ <button @click="actions.handleClick">
133
+ <button @click="actions.increment">
134
+ <button @click="actions.toggleTodo(todo.id)"> <!-- curried OK -->
135
+ ```
136
+
137
+ ### ⚠ REGLA #5 — `v-for` colección SIEMPRE namespaced
138
+
139
+ ```html
140
+ <!-- ❌ -->
141
+ <li v-for="item in items"> <!-- VFOR_NOT_NAMESPACED -->
142
+ <li v-for="x in getList()"> <!-- llamada a función -->
143
+
144
+ <!-- ✅ -->
145
+ <li v-for="item in state.items.value">
146
+ <li v-for="tab in computed.tabs.value">
147
+ <li v-for="opt in props.options">
148
+ <li v-for="(item, idx) in state.items.value"> <!-- con índice -->
149
+ ```
150
+
151
+ ### ⚠ REGLA #6 — `v-if` y `v-else-if` deben usar acceso namespaced (con `.`)
152
+
153
+ ```html
154
+ <!-- ⚠ warning -->
155
+ <div v-if="visible"> <!-- VIF_NOT_NAMESPACED -->
156
+
157
+ <!-- ✅ -->
158
+ <div v-if="state.visible.value">
159
+ <div v-if="computed.isReady.value">
160
+ <div v-if="props.active">
161
+ <div v-if="todo.done"> <!-- variable de loop con dot -->
162
+ <div v-if="state.count.value > 0">
163
+ ```
164
+
165
+ ### ⚠ REGLA #7 — NO existe `v-model`. Input controlado explícito
166
+
167
+ ```html
168
+ <!-- ❌ -->
169
+ <input v-model="state.email" />
170
+
171
+ <!-- ✅ -->
172
+ <input :value="state.email.value" @input="actions.onEmailInput" />
173
+ ```
174
+
175
+ ```typescript
176
+ function onEmailInput(e: Event) {
177
+ email.value = (e.target as HTMLInputElement).value
178
+ }
179
+ ```
180
+
181
+ ### ⚠ REGLA #8 — NO existe `ref()` ni `reactive()`. Solo `signal`/`computed`
182
+
183
+ ```typescript
184
+ // ❌
185
+ const x = ref(0) // REF_REACTIVE_USAGE
186
+ const s = reactive({ a: 1, b: 2 }) // REF_REACTIVE_USAGE
187
+
188
+ // ✅
189
+ const x = signal(0)
190
+ const a = signal(1); const b = signal(2) // un signal por propiedad
191
+ ```
192
+
193
+ ### ⚠ REGLA #9 — NO existe `provide`/`inject`. Estado compartido = `defineStore`
194
+
195
+ ```typescript
196
+ // ❌
197
+ provide('key', value) // PROVIDE_INJECT_USAGE
198
+ const v = inject('key')
199
+
200
+ // ✅
201
+ // stores/auth.store.ts
202
+ export const useAuthStore = defineStore('auth', { state, getters, actions })
203
+ // componente
204
+ const auth = useAuthStore()
205
+ ```
206
+
207
+ ### ⚠ REGLA #10 — `fetch()` PROHIBIDO en componente. Siempre via service
208
+
209
+ ```typescript
210
+ // ❌
211
+ onMount(() => fetch('/api/users')) // DIRECT_FETCH_IN_COMPONENT
212
+
213
+ // ✅
214
+ // services/user.service.ts
215
+ export const UserService = createService(
216
+ { baseUrl: '/api/users' },
217
+ { async getAll() { return this.get<User[]>('/') } },
218
+ )
219
+ // componente
220
+ import { UserService } from '../services/user.service'
221
+ function load() { UserService.getAll().then(...) }
222
+ ```
223
+
224
+ ### ⚠ REGLA #11 — NO lógica en directivas (`++`, `--`, ternario complejo)
225
+
226
+ ```html
227
+ <!-- ❌ -->
228
+ <button @click="state.count.value++"> <!-- LOGIC_IN_TEMPLATE -->
229
+ <div :class="state.x.value > 0 ? 'a' : 'b'"> <!-- ternario en :class -->
230
+
231
+ <!-- ✅ -->
232
+ <button @click="actions.increment">
233
+ <div :class="computed.xClass.value"> <!-- mover a computed -->
234
+ ```
235
+
236
+ ### ⚠ REGLA #12 — Actions como `function declaration`, no arrow
237
+
238
+ ```typescript
239
+ // ❌
240
+ actions: { submit: () => { ... } } // ARROW_FUNCTION_ACTION
241
+ const submit = () => { ... }
242
+ return { actions: { submit } } // ARROW_FUNCTION_ACTION
243
+
244
+ // ✅
245
+ function submit() { ... }
246
+ return { actions: { submit } }
247
+ ```
248
+
249
+ ### ⚠ REGLA #13 — Componentes hijos: PascalCase + import explícito
250
+
251
+ ```html
252
+ <!-- ❌ -->
253
+ <usercard :prop="x" /> <!-- minúscula = elemento HTML -->
254
+
255
+ <!-- ✅ -->
256
+ <UserCard :prop="state.x.value" />
257
+ ```
258
+
259
+ ```typescript
260
+ // import obligatorio
261
+ import UserCard from './UserCard.ark'
262
+ ```
263
+
264
+ ### ⚠ REGLA #14 — `mount(component, selector)` toma string, no HTMLElement
265
+
266
+ ```typescript
267
+ // ❌
268
+ mount(App, document.getElementById('app')!)
269
+
270
+ // ✅
271
+ mount(App, '#app')
272
+ ```
273
+
274
+ ### ⚠ REGLA #15 — Imports SOLO de `'arckode-ui'` o `'arckode-ui/vite'`
275
+
276
+ ```typescript
277
+ // ❌
278
+ import { ref } from 'vue'
279
+ import { useState } from 'react'
280
+ import { defineStore } from 'arckode-ui/store' // no existe ese subpath
281
+
282
+ // ✅
283
+ import { defineComponent, signal, computed, defineStore, createService } from 'arckode-ui'
284
+ import { arkcodeUi } from 'arckode-ui/vite'
285
+ ```
286
+
287
+ Plugin se llama **`arkcodeUi`** (no `arckodeUi`, no `arckodePlugin`).
288
+
289
+ ### ⚠ REGLA #16 — Orden recomendado del `setup()` (convención, no enforced)
290
+
291
+ ```
292
+ 1. signals → const x = signal(...)
293
+ 2. computed → const y = computed(...)
294
+ 3. lifecycle → onMount(...)
295
+ 4. watch → watch(x, ...)
296
+ 5. actions → function doSomething() {}
297
+ 6. return → return { state, computed, actions }
298
+ ```
299
+
300
+ El analyzer NO enforza este orden. Es para legibilidad y para que la IA prediga la posición.
301
+
302
+ ---
303
+
304
+ ## 4. ÁRBOLES DE DECISIÓN (Si te piden X, hacé Y)
305
+
306
+ ### ¿Te piden CREAR un componente nuevo?
307
+
308
+ ```
309
+ ¿Qué tipo de componente?
310
+
311
+ → Página (URL accesible)
312
+ Comando: ark generate page <kebab/o/[id]>
313
+ Path: src/pages/<name>.ark
314
+ Mountable directamente desde el file-system router
315
+
316
+ → Layout (wrapper de carpeta)
317
+ Comando: ark generate layout <name>
318
+ Path: src/pages/<name>/_layout.ark
319
+ Tiene <slot /> obligatorio
320
+
321
+ → Componente de dominio (UserCard, ProductList, etc.)
322
+ Comando: ark generate component <PascalCase>
323
+ Path: src/components/features/<Name>.ark
324
+
325
+ → Primitivo de UI (Button, Input, Modal)
326
+ Manual: src/components/ui/<Name>.ark
327
+ Igual estructura que componente de dominio
328
+
329
+ → Wrapper con contenido inyectable
330
+ Manual: usar <slot /> en el template, ver patrón en §6
331
+ ```
332
+
333
+ ### ¿Te piden AGREGAR estado a un componente?
334
+
335
+ ```
336
+ ¿El dato es local al componente?
337
+ SÍ → signal() en setup() — REGLA #8
338
+
339
+ ¿El dato se deriva de otro signal? (transformación pura)
340
+ SÍ → computed() — readonly, sin side effects
341
+
342
+ ¿El dato es controlado por el padre?
343
+ SÍ → prop con type — REGLA #1
344
+
345
+ ¿El padre necesita saber cuándo cambia?
346
+ SÍ → emit (kebab-case) — REGLA #2
347
+
348
+ ¿Otros componentes sin relación padre↔hijo lo necesitan?
349
+ SÍ → defineStore() — REGLA #9 (no provide/inject)
350
+
351
+ ¿Necesita disparar side effect cuando cambia un signal?
352
+ SÍ → watch(signal, callback)
353
+
354
+ ¿Necesita side effect con auto-suscripción a múltiples signals?
355
+ SÍ → effect(() => ...)
356
+ ```
357
+
358
+ ### ¿Te piden COMUNICAR componentes?
359
+
360
+ ```
361
+ ¿Padre → Hijo?
362
+ → prop con type (datos)
363
+ → callback como prop (acciones que el hijo invoca)
364
+
365
+ ¿Hijo → Padre?
366
+ Opción A (canónica): emit + @event en el padre
367
+ Hijo: emits: ['save'], emit('save', payload)
368
+ Padre: <Child @save="actions.onSave" />
369
+ El handler del padre recibe el payload directo (sin Event)
370
+
371
+ Opción B (callback como prop): cuando el hijo no "owns" el evento
372
+ Padre: <Child :onSave="actions.handle" />
373
+ Hijo: props.onSave declarado como Function
374
+
375
+ ¿Hermanos (sin relación)?
376
+ → defineStore() — signals globales compartidos
377
+
378
+ ¿Más de 2 niveles de props drilling?
379
+ → defineStore()
380
+ ```
381
+
382
+ ### ¿Te piden RENDERIZAR una lista?
383
+
384
+ ```
385
+ 1. La colección DEBE ser state.x.value / computed.x.value / props.x — REGLA #5
386
+ 2. Sintaxis: v-for="item in <colección>" o v-for="(item, idx) in <colección>"
387
+ 3. Si el handler necesita el item: curried action
388
+ function toggle(id: string) { return () => { ... } }
389
+ @click="actions.toggle(item.id)"
390
+ 4. Si :class depende de un signal Y del item: computed que retorna closure con SNAPSHOT
391
+ const itemClass = computed(() => {
392
+ const active = state.activeId.value // ← lee la dep ACÁ
393
+ return (item) => item.id === active ? 'A' : 'B'
394
+ })
395
+ :class="computed.itemClass.value(item)"
396
+ ```
397
+
398
+ ### ¿Te piden HACER UN FETCH?
399
+
400
+ ```
401
+ NUNCA en el componente — REGLA #10
402
+
403
+ PASO 1: crear/modificar service en src/services/<dominio>.service.ts
404
+ export const UserService = createService(
405
+ { baseUrl: '/api/users' },
406
+ {
407
+ async getAll() { return this.get<User[]>('/') },
408
+ async getById(id: string) { return this.get<User>(`/${id}`) },
409
+ },
410
+ )
411
+
412
+ PASO 2: en el componente, import y llamar desde action u onMount
413
+ import { UserService } from '../services/user.service'
414
+
415
+ const users = signal<User[]>([])
416
+ onMount(async () => {
417
+ users.value = await UserService.getAll()
418
+ })
419
+ ```
420
+
421
+ ### ¿Te piden MODIFICAR un componente existente?
422
+
423
+ ```
424
+ PASO 1: Leer el .ark completo (no asumir su shape)
425
+ PASO 2: Identificar dónde va el cambio según el ORDEN del setup() (§3 REGLA #16):
426
+ - Nuevo signal → bloque 1
427
+ - Nuevo computed → bloque 2
428
+ - Nuevo onMount → bloque 3
429
+ - Nuevo watch → bloque 4
430
+ - Nuevo action → bloque 5
431
+ - Nueva prop → en props: {} con type
432
+ - Nuevo emit → en emits: [] kebab-case
433
+ - Nuevo slot → en el template (cuidado, suma una API pública nueva)
434
+ PASO 3: Si agregás un signal/computed/action, registrarlo en el return
435
+ PASO 4: Correr ark analyze para confirmar 0 errors nuevos
436
+ ```
437
+
438
+ ### ¿El analyzer reportó un error?
439
+
440
+ ```
441
+ PASO 1: Leer el message Y el campo `Fix:` (siempre viene con sugerencia concreta)
442
+ PASO 2: Si el fix dice "Reemplazar X por Y": aplicar literal
443
+ PASO 3: Si el fix dice "Crear un X y referenciarlo": crear primero, luego referenciar
444
+ PASO 4: Re-correr ark analyze para confirmar que el error desapareció
445
+ PASO 5: Si aparece OTRO error: repetir (a veces un fix expone uno latente)
446
+
447
+ NO ignorar errors. NO desactivar reglas. NO ignorar warnings sin entender por qué aparecen.
448
+ ```
449
+
450
+ ---
451
+
452
+ ## 5. PLANTILLA CANÓNICA — componente completo
453
+
454
+ Copiar y adaptar. Toda IA debe partir de esta plantilla para crear un componente nuevo.
455
+
456
+ ```html
457
+ <template>
458
+ <div class="user-card">
459
+ <!-- Interpolación -->
460
+ <h2>{{ props.name }}</h2>
461
+ <p>{{ state.bio.value }}</p>
462
+ <span>{{ computed.followLabel.value }}</span>
463
+
464
+ <!-- Evento — SIEMPRE actions.xxx (REGLA #4) -->
465
+ <button @click="actions.follow">{{ computed.followLabel.value }}</button>
466
+
467
+ <!-- Input controlado — REGLA #7 -->
468
+ <input :value="state.bio.value" @input="actions.onBioInput" />
469
+
470
+ <!-- Condicional -->
471
+ <div v-if="state.following.value">Siguiendo</div>
472
+ <div v-else>No seguido</div>
473
+
474
+ <!-- Lista — colección namespaced (REGLA #5) -->
475
+ <div v-for="tag in state.tags.value">{{ tag }}</div>
476
+
477
+ <!-- v-show — mantiene en DOM -->
478
+ <div v-show="state.panelVisible.value">Panel</div>
479
+ </div>
480
+ </template>
481
+
482
+ <script lang="ts">
483
+ import { defineComponent, signal, computed, watch, onMount } from 'arckode-ui'
484
+
485
+ export default defineComponent({
486
+ name: 'UserCard', // REGLA: PascalCase obligatorio
487
+
488
+ props: { // REGLA #1
489
+ name: { type: String, required: true },
490
+ userId: { type: String, required: true },
491
+ },
492
+
493
+ emits: ['follow', 'unfollow'], // REGLA #2: kebab-case
494
+
495
+ setup(props, { emit }) {
496
+ // ── 1. signals ──
497
+ const bio = signal('')
498
+ const following = signal(false)
499
+ const tags = signal<string[]>([])
500
+ const panelVisible = signal(true)
501
+
502
+ // ── 2. computed ──
503
+ const followLabel = computed(() =>
504
+ following.value ? 'Dejar de seguir' : 'Seguir'
505
+ )
506
+
507
+ // ── 3. lifecycle ──
508
+ onMount(() => {
509
+ bio.value = 'Cargando...'
510
+ return () => { /* cleanup opcional */ }
511
+ })
512
+
513
+ // ── 4. watch ──
514
+ watch(following, (newVal) => { console.log('following:', newVal) })
515
+
516
+ // ── 5. actions — REGLA #12: function declaration ──
517
+ function follow() {
518
+ following.value = !following.value
519
+ emit(following.value ? 'follow' : 'unfollow', { userId: props.userId })
520
+ }
521
+ function onBioInput(e: Event) {
522
+ bio.value = (e.target as HTMLInputElement).value
523
+ }
524
+
525
+ // ── 6. return — REGLA #3: SOLO state/computed/actions ──
526
+ return {
527
+ state: { bio, following, tags, panelVisible },
528
+ computed: { followLabel },
529
+ actions: { follow, onBioInput },
530
+ }
531
+ },
532
+ })
533
+ </script>
534
+
535
+ <style scoped>
536
+ .user-card { padding: 1rem; }
537
+ </style>
538
+ ```
539
+
540
+ ---
541
+
542
+ ## 6. PLANTILLAS POR TIPO
543
+
544
+ ### 6.1 — Componente atómico (Badge, Button)
545
+
546
+ ```html
547
+ <template>
548
+ <span :class="computed.classes.value"><slot /></span>
549
+ </template>
550
+ <script lang="ts">
551
+ import { defineComponent, computed } from 'arckode-ui'
552
+ const VARIANTS: Record<string, string> = { low: '...', high: '...' }
553
+ export default defineComponent({
554
+ name: 'Badge',
555
+ props: { variant: { type: String, required: true } },
556
+ emits: [],
557
+ setup(props) {
558
+ const classes = computed(() => VARIANTS[props.variant as string] ?? VARIANTS['low'])
559
+ return { state: {}, computed: { classes }, actions: {} }
560
+ },
561
+ })
562
+ </script>
563
+ ```
564
+
565
+ ### 6.2 — Wrapper con slot (Modal, Card)
566
+
567
+ ```html
568
+ <template>
569
+ <div v-show="props.visible" class="modal">
570
+ <div class="header">
571
+ <h2>{{ props.title }}</h2>
572
+ <button @click="actions.close">✕</button>
573
+ </div>
574
+ <div class="body"><slot /></div>
575
+ </div>
576
+ </template>
577
+ <script lang="ts">
578
+ import { defineComponent } from 'arckode-ui'
579
+ export default defineComponent({
580
+ name: 'Modal',
581
+ props: {
582
+ visible: { type: Boolean, required: true },
583
+ title: { type: String, required: true },
584
+ onClose: { type: Function, required: true },
585
+ },
586
+ emits: [],
587
+ setup(props) {
588
+ function close() { (props.onClose as () => void)() }
589
+ return { state: {}, computed: {}, actions: { close } }
590
+ },
591
+ })
592
+ </script>
593
+ ```
594
+
595
+ ### 6.3 — Lista con curried action
596
+
597
+ ```html
598
+ <template>
599
+ <div v-for="item in state.items.value">
600
+ <button @click="actions.toggle(item.id)">{{ item.done ? '✓' : '○' }}</button>
601
+ <span>{{ item.text }}</span>
602
+ </div>
603
+ </template>
604
+ <script lang="ts">
605
+ import { defineComponent, signal } from 'arckode-ui'
606
+ interface Item { id: string; text: string; done: boolean }
607
+ export default defineComponent({
608
+ name: 'ItemList',
609
+ props: {},
610
+ emits: [],
611
+ setup() {
612
+ const items = signal<Item[]>([])
613
+ function toggle(id: string) {
614
+ return () => {
615
+ items.value = items.value.map(i => i.id === id ? { ...i, done: !i.done } : i)
616
+ }
617
+ }
618
+ return { state: { items }, computed: {}, actions: { toggle } }
619
+ },
620
+ })
621
+ </script>
622
+ ```
623
+
624
+ ---
625
+
626
+ ## 7. STORE — estado global compartido
627
+
628
+ ```typescript
629
+ // stores/user.store.ts
630
+ import { defineStore, signal, computed } from 'arckode-ui'
631
+
632
+ interface UserDTO { id: string; name: string }
633
+
634
+ export const useUserStore = defineStore('user', {
635
+ state: {
636
+ current: signal<UserDTO | null>(null),
637
+ loading: signal(false),
638
+ },
639
+ getters: {
640
+ isLoggedIn: computed(() => useUserStore().state.current.value !== null),
641
+ },
642
+ actions: {
643
+ async login(email: string, password: string) {
644
+ const store = useUserStore()
645
+ store.state.loading.value = true
646
+ // ...
647
+ },
648
+ logout() { useUserStore().state.current.value = null },
649
+ },
650
+ })
651
+ ```
652
+
653
+ Uso en componente:
654
+ ```typescript
655
+ const userStore = useUserStore()
656
+ return {
657
+ state: { current: userStore.state.current },
658
+ computed: { isLoggedIn: userStore.getters.isLoggedIn },
659
+ actions: { logout: userStore.actions.logout },
660
+ }
661
+ ```
662
+
663
+ **Patrón clave:** dentro de getters/actions, llamar `useUserStore()` para acceder al state. Es feo pero es la API real verificada en tests.
664
+
665
+ ---
666
+
667
+ ## 8. SERVICE — HTTP
668
+
669
+ ```typescript
670
+ // services/product.service.ts
671
+ import { createService } from 'arckode-ui'
672
+
673
+ interface Product { id: string; name: string; price: number }
674
+
675
+ export const ProductService = createService(
676
+ { baseUrl: '/api/products', timeout: 5000 },
677
+ {
678
+ async getAll() { return this.get<Product[]>('/') },
679
+ async getById(id: string) { return this.get<Product>(`/${id}`) },
680
+ async create(data: Omit<Product, 'id'>) { return this.post<Product>('/', data) },
681
+ async update(id: string, data: Partial<Product>) { return this.put<Product>(`/${id}`, data) },
682
+ async remove(id: string) { return this.delete<void>(`/${id}`) },
683
+ },
684
+ )
685
+ ```
686
+
687
+ **Firma del createService: DOS ARGUMENTOS (options, definition).** Verificado en tests.
688
+
689
+ ---
690
+
691
+ ## 9. CLI — comandos exactos
692
+
693
+ ```bash
694
+ ark new <nombre> # scaffold proyecto nuevo
695
+ ark generate component <PascalCase> # alias: ark g c
696
+ ark generate page <name> # alias: ark g p — name puede ser kebab o users/[id]
697
+ ark generate store <camelCase> # alias: ark g s
698
+ ark generate service <PascalCase> # alias: ark g sv
699
+ ark generate layout <name> # alias: ark g l
700
+ ark analyze [--json] # detecta violations en .ark
701
+ ark routes # lista rutas detectadas en src/pages/
702
+ ```
703
+
704
+ Naming OBLIGATORIO (el generator lo enforza):
705
+ - Component: PascalCase (Button, UserCard)
706
+ - Store: camelCase (cart, userPreferences)
707
+ - Service: PascalCase (Product, User)
708
+ - Page/Layout: kebab-case con segmentos
709
+
710
+ ---
711
+
712
+ ## 10. VITE — setup del proyecto consumidor
713
+
714
+ ```typescript
715
+ // vite.config.ts
716
+ import { defineConfig } from 'vite'
717
+ import { arkcodeUi } from 'arckode-ui/vite' // REGLA #15: nombre EXACTO
718
+
719
+ export default defineConfig({
720
+ plugins: [
721
+ arkcodeUi({
722
+ analyzer: {
723
+ failOnWarnings: false, // dev: no bloquea. prod: bloquea errors.
724
+ ignore: [], // códigos a ignorar
725
+ },
726
+ }),
727
+ ],
728
+ })
729
+ ```
730
+
731
+ ```html
732
+ <!-- index.html -->
733
+ <div id="app"></div>
734
+ <script type="module" src="/src/main.ts"></script>
735
+ ```
736
+
737
+ ```typescript
738
+ // src/main.ts
739
+ import { mount } from 'arckode-ui'
740
+ import App from './App.ark'
741
+
742
+ mount(App, '#app') // REGLA #14: selector string
743
+ ```
744
+
745
+ ---
746
+
747
+ ## 11. ANALYZER OUTPUT — cómo leerlo
748
+
749
+ Cada violation viene con `Fix:` cuando es determinístico. **Leer el Fix y aplicarlo literal.**
750
+
751
+ ```
752
+ [arckode-ui] UserCard.ark:5
753
+ HANDLER_NOT_IN_ACTIONS: El handler "handleClick" no está namespaced.
754
+
755
+ > 5 | <button @click="handleClick">
756
+ Fix: Reemplazar "handleClick" por "actions.handleClick" y asegurarse de que la función esté declarada en setup() y exportada en el return.actions.
757
+ ```
758
+
759
+ Códigos del analyzer (16):
760
+
761
+ | Código | Severity | Regla violada |
762
+ |--------|----------|---------------|
763
+ | MISSING_TEMPLATE | error | — (estructura) |
764
+ | MISSING_SCRIPT | error | — |
765
+ | MISSING_LANG_TS | error | — |
766
+ | WRONG_TEMPLATE_ORDER | error | — |
767
+ | MISSING_COMPONENT_NAME | error | — |
768
+ | PROP_MISSING_TYPE | error | REGLA #1 |
769
+ | EMIT_CAMELCASE | error | REGLA #2 |
770
+ | SETUP_UNKNOWN_RETURN_KEY | error | REGLA #3 |
771
+ | HANDLER_NOT_IN_ACTIONS | error | REGLA #4 |
772
+ | VFOR_NOT_NAMESPACED | error | REGLA #5 |
773
+ | VIF_NOT_NAMESPACED | warning | REGLA #6 |
774
+ | REF_REACTIVE_USAGE | error | REGLA #8 |
775
+ | PROVIDE_INJECT_USAGE | error | REGLA #9 |
776
+ | DIRECT_FETCH_IN_COMPONENT | error | REGLA #10 |
777
+ | LOGIC_IN_TEMPLATE | error | REGLA #11 |
778
+ | ARROW_FUNCTION_ACTION | warning | REGLA #12 |
779
+
780
+ ---
781
+
782
+ ## 12. CHECKLIST POST-CÓDIGO (antes de declarar "listo")
783
+
784
+ ```
785
+ □ npx ark analyze → 0 errors
786
+ □ Si tocaste el framework: bun test → todos verdes
787
+ □ Si tocaste UI: arrancar dev server y probar en navegador
788
+ □ Si agregaste prop/emit nuevo: actualizar consumidores
789
+ □ Si modificaste un componente que tenía tests: re-correr esos tests
790
+ □ ¿Toqué exports del barrel src/index.ts del framework? → es breaking change
791
+ □ ¿Cambié el código o severity de una violation? → es breaking change
792
+ ```
793
+
794
+ **Si el analyzer reporta errors → corregirlos según el campo Fix.**
795
+ **Si los tests fallan → corregir antes de continuar.**
796
+ **No declarar "listo" hasta que los 2 estén verdes.**
797
+
798
+ ---
799
+
800
+ ## 13. ANTI-PATTERNS — referencia rápida (todo viola alguna regla numerada)
801
+
802
+ ```typescript
803
+ // ❌ REGLA #1
804
+ props: { name: { required: true } }
805
+ props: { name: { type: 'string' } }
806
+
807
+ // ❌ REGLA #2
808
+ emits: ['userSaved']
809
+
810
+ // ❌ REGLA #3
811
+ return { state: {}, computed: {}, actions: {}, methods: {} }
812
+
813
+ // ❌ REGLA #4
814
+ @click="handleClick"
815
+ @click="state.count.value++"
816
+
817
+ // ❌ REGLA #5
818
+ v-for="item in items"
819
+ v-for="x in getList()"
820
+
821
+ // ❌ REGLA #6 (warning)
822
+ v-if="visible"
823
+
824
+ // ❌ REGLA #7
825
+ <input v-model="state.name" />
826
+
827
+ // ❌ REGLA #8
828
+ const x = ref(0)
829
+ const s = reactive({})
830
+
831
+ // ❌ REGLA #9
832
+ provide('key', val)
833
+ inject('key')
834
+
835
+ // ❌ REGLA #10
836
+ onMount(() => fetch('/api/x'))
837
+
838
+ // ❌ REGLA #11
839
+ @click="state.count.value++"
840
+ :class="state.x.value ? 'a' : 'b'"
841
+
842
+ // ❌ REGLA #12
843
+ actions: { submit: () => {} }
844
+
845
+ // ❌ REGLA #13
846
+ <usercard :prop="x" />
847
+
848
+ // ❌ REGLA #14
849
+ mount(App, document.getElementById('app')!)
850
+
851
+ // ❌ REGLA #15
852
+ import { ref } from 'vue'
853
+ import { arckodePlugin } from 'arckode-ui/vite' // nombre incorrecto
854
+ ```
855
+
856
+ ---
857
+
858
+ ## 14. ESTADO ACTUAL DEL FRAMEWORK (v0.1.1)
859
+
860
+ - ✅ 314 tests passing
861
+ - ✅ 16 reglas del analyzer con `Fix:` field
862
+ - ✅ APIs exportadas: signal, computed, watch, effect, defineComponent, onMount, onUnmount, onUpdate, h, mount, mountComponent, defineStore, createService, ArkServiceError, createRouter, navigate, useRoute, getCurrentPath
863
+ - ✅ Subpath `arckode-ui/vite` exporta `arkcodeUi`
864
+ - ✅ CLI `ark` con shebang ejecutable
865
+ - ✅ 6 sub-skills distribuidos en `skills/`
866
+ - ✅ 2 ejemplos completos: `kitchen-sink` (demo de features), `tasks` (app real con sidebar/modal/form)