From 06e2ff88d795345d8f19f3838d4ee9a1f046f631 Mon Sep 17 00:00:00 2001 From: Matias Trujillo Date: Sat, 23 May 2026 13:16:55 -0400 Subject: [PATCH 01/17] tmp: skills --- .agents/skills/atomico/SKILL.md | 114 +++++++++++++++++ .agents/skills/atomico/examples/1-generic.md | 46 +++++++ .agents/skills/atomico/examples/2-todo-app.md | 65 ++++++++++ .../atomico/examples/3-async-suspense.md | 82 ++++++++++++ .agents/skills/atomico/examples/4-context.md | 55 ++++++++ .../skills/atomico/examples/5-slot-slider.md | 58 +++++++++ .../skills/atomico/examples/6-other-hooks.md | 61 +++++++++ .../atomico/rules/component-creation.md | 80 ++++++++++++ .agents/skills/atomico/rules/jsx-patterns.md | 46 +++++++ .../skills/atomico/rules/props-declaration.md | 97 +++++++++++++++ .../skills/atomico/rules/state-management.md | 62 ++++++++++ .../atomico/rules/styling-application.md | 108 ++++++++++++++++ developer-experience/skills-evaluator/eval.js | 117 ++++++++++++++++++ .../skills-evaluator/prompts.json | 14 +++ 14 files changed, 1005 insertions(+) create mode 100644 .agents/skills/atomico/SKILL.md create mode 100644 .agents/skills/atomico/examples/1-generic.md create mode 100644 .agents/skills/atomico/examples/2-todo-app.md create mode 100644 .agents/skills/atomico/examples/3-async-suspense.md create mode 100644 .agents/skills/atomico/examples/4-context.md create mode 100644 .agents/skills/atomico/examples/5-slot-slider.md create mode 100644 .agents/skills/atomico/examples/6-other-hooks.md create mode 100644 .agents/skills/atomico/rules/component-creation.md create mode 100644 .agents/skills/atomico/rules/jsx-patterns.md create mode 100644 .agents/skills/atomico/rules/props-declaration.md create mode 100644 .agents/skills/atomico/rules/state-management.md create mode 100644 .agents/skills/atomico/rules/styling-application.md create mode 100644 developer-experience/skills-evaluator/eval.js create mode 100644 developer-experience/skills-evaluator/prompts.json diff --git a/.agents/skills/atomico/SKILL.md b/.agents/skills/atomico/SKILL.md new file mode 100644 index 0000000..175a943 --- /dev/null +++ b/.agents/skills/atomico/SKILL.md @@ -0,0 +1,114 @@ +--- +name: atomico +description: > + Core skill for creating web components and UI components. Triggers when the user asks to + "create component", "create webcomponent", "create ui", "create view", or explicitly mentions "atomico". + If no framework is explicitly specified, ALWAYS assume Atomico and use this skill. + Atomico components are functional web components that return JSX with a mandatory `` root element. + Do NOT use React or VanillaJS if this skill triggers unless the user explicitly forces it. +license: MIT +compatibility: "Atomico >=2.0, TypeScript >=5.0, JSX via atomico/jsx-runtime" +metadata: + category: core + priority: highest +--- + +# Atomico Web Components Best Practices + +Comprehensive guide for creating Web Components using Atomico. Atomico uses a functional approach with JSX, similar to React, but strictly outputs standard Custom Elements. + +## 1. Generic Component Guide (The Atomico Core Pattern) + +This example combines all the skills you must use when creating a component. Use the `c(render, config)` signature exclusively. + +```tsx +// Skill: imports (atomico core) +import { c, css, useProp } from "atomico"; + +// Skill: rules/component-creation.md +export const MyCounter = c( + ({ message }) => { + // Skill: rules/state-management.md & rules/hooks-api.md + const [counter, setCounter] = useProp("counter"); + + // Skill: rules/component-creation.md + return ( + +

{message}

+

{counter}

+ +
+ ); + }, + { + // Skill: rules/props-declaration.md + props: { + message: String, + counter: { type: Number, value: () => 0, reflect: true } + }, + // Skill: rules/styling-application.md + styles: css` + :host { + display: block; + color: blue; + } + :host([counter="0"]) { + color: red; + } + ` + } +); + +customElements.define("my-counter", MyCounter); +``` + +## 2. Skill Rules Context + +For specific details on how to apply the core patterns, review the following rule documents. These files evaluate the correct application of the skills: + +- `rules/component-creation.md`: The `c()` function, JSX ``, and exporting instances. +- `rules/jsx-patterns.md`: Using Constructors vs string tags for component composition in JSX. +- `rules/props-declaration.md`: Variants, types, `reflect: true`, and factory default values. +- `rules/styling-application.md`: `` usage and the `css` tagged template. +- `rules/state-management.md`: Exposing public attributes via `useProp` vs `useState`. +- `rules/hooks-api.md`: Deep dive into Atomico's internal lifecycle hooks. + +## 3. Examples + +For complete implementation examples covering various UI requirements, check the `examples/` directory: + +- `examples/1-generic.md`: A basic component covering props, state, and CSS. +- `examples/2-todo-app.md`: A Todo List handling arrays and events. +- `examples/3-async-suspense.md`: Data fetching using `usePromise`, `useAsync`, and suspense. +- `examples/4-context.md`: Context API usage for sharing state without prop drilling. +- `examples/5-slot-slider.md`: Manual slot assignment for building sliders or advanced layouts. +- `examples/6-other-hooks.md`: Usage of `useId`, `useHost`, and other utility hooks. + +## 4. Hooks Context + +Atomico shares logic with React, but includes its own hooks specifically designed for the lifecycle of Custom Elements. + +### Atomico-Specific Hooks +These hooks are unique to Atomico. Use them to interact with the web component lifecycle and DOM node. See `rules/hooks-api.md` for full documentation. + +| Hook | Usage Context in Atomico | Documentation | +|------|-------------------------|---------------| +| `useProp("name")` | **Crucial for public state**. Getter/setter linked to a declared `prop`. | [rules/hooks-api.md](rules/hooks-api.md) | +| `useHost()` | Returns the reference to the current custom element (`this`). | [rules/hooks-api.md](rules/hooks-api.md) | +| `useEvent("name", opts)` | Dispatches `CustomEvent`s. | [rules/hooks-api.md](rules/hooks-api.md) | +| `useUpdate()` | Forces a re-render of the component. Use sparingly. | [rules/hooks-api.md](rules/hooks-api.md) | +| `usePromise(promise)` | Resolves a promise and returns its status and value. | [rules/hooks-api.md](rules/hooks-api.md) | +| `useAsync(fn, deps)` | Executes async tasks safely, handling cleanup. | [rules/hooks-api.md](rules/hooks-api.md) | + +### React-Equivalent Hooks +Atomico fully supports the standard React hooks API. **Assume their behavior, rules of hooks, and logic are identical to React.** + +| Hook | Behavior | +|------|----------| +| `useState(init)` | Local, internal, private state only. Do not use for data that should be passed as HTML attributes. | +| `useEffect(fn, deps)` | Side effects after render. Same behavior as React. | +| `useLayoutEffect` | Synchronous side effects. Same behavior as React. | +| `useMemo(fn, deps)` | Memoization of values. Same behavior as React. | +| `useCallback(fn)` | Memoization of callbacks. Same behavior as React. | +| `useRef(init)` | Mutable reference persisting across renders. Same behavior as React. | +| `useId()` | Unique ID generator for accessibility attributes. | diff --git a/.agents/skills/atomico/examples/1-generic.md b/.agents/skills/atomico/examples/1-generic.md new file mode 100644 index 0000000..878119f --- /dev/null +++ b/.agents/skills/atomico/examples/1-generic.md @@ -0,0 +1,46 @@ +# Example: Generic Component + +A generic web component summarizing the basics of Atomico 2.0: + +```tsx +import { c, css, useProp } from "atomico"; + +export const UserCard = c( + ({ name, role }) => { + const [active, setActive] = useProp("active"); + + return ( + +
+

{name}

+

{role}

+ +
+
+ ); + }, + { + props: { + name: { type: String, value: () => "Guest" }, + role: { type: String, value: () => "Visitor" }, + active: { type: Boolean, reflect: true } + }, + styles: css` + :host { + display: block; + border: 1px solid #ccc; + padding: 1rem; + border-radius: 8px; + } + :host([active]) { + border-color: green; + background: #eaffea; + } + ` + } +); + +customElements.define("user-card", UserCard); +``` diff --git a/.agents/skills/atomico/examples/2-todo-app.md b/.agents/skills/atomico/examples/2-todo-app.md new file mode 100644 index 0000000..c3e298b --- /dev/null +++ b/.agents/skills/atomico/examples/2-todo-app.md @@ -0,0 +1,65 @@ +# Example: Todo App + +A component handling array rendering and event dispatching. + +```tsx +import { c, css, useProp, useEvent } from "atomico"; + +export const TodoList = c( + ({ title }) => { + const [tasks, setTasks] = useProp("tasks"); + const dispatchAdd = useEvent("add-task", { bubbles: true }); + + const onFormSubmit = (e) => { + e.preventDefault(); + const input = e.target.elements.task; + const newTask = { id: Date.now(), text: input.value, done: false }; + + // Dispatch event for parents to listen to + dispatchAdd(newTask); + + // Update internal state + setTasks([...tasks, newTask]); + input.value = ""; + }; + + const toggleTask = (id) => { + setTasks(tasks.map(t => t.id === id ? { ...t, done: !t.done } : t)); + }; + + return ( + +

{title}

+
+ + +
+
    + {tasks.map(task => ( +
  • + toggleTask(task.id)} + /> + {task.text} +
  • + ))} +
+
+ ); + }, + { + props: { + title: { type: String, value: () => "My Tasks" }, + tasks: { type: Array, value: () => [] } + }, + styles: css` + :host { display: block; } + .done { text-decoration: line-through; color: gray; } + ` + } +); + +customElements.define("todo-list", TodoList); +``` diff --git a/.agents/skills/atomico/examples/3-async-suspense.md b/.agents/skills/atomico/examples/3-async-suspense.md new file mode 100644 index 0000000..7875835 --- /dev/null +++ b/.agents/skills/atomico/examples/3-async-suspense.md @@ -0,0 +1,82 @@ +# Example: Async and Suspense + +Demonstrating the correct use of `usePromise` in a child component and `useSuspense` in a parent component to orchestrate loading states. + +> [!IMPORTANT] +> `useSuspense` only listens to its **children**. It cannot listen to promises in the same component, because async operations like `useAsync` block rendering. If you need to observe a promise in the same component, use `usePromise`. + +```tsx +import { c, css, usePromise, useSuspense } from "atomico"; + +// --- CHILD COMPONENT --- +// Fetches data and broadcasts its state upwards +const fetchUserData = async (userId) => { + const res = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`); + if (!res.ok) throw new Error("User not found"); + return res.json(); +}; + +export const UserProfile = c( + ({ userId }) => { + // usePromise watches the promise and notifies any parent Suspense boundary + const userPromise = usePromise(() => fetchUserData(userId), [userId]); + + return ( + + {/* Render fallback inside if not using a parent boundary, or let the parent handle it */} + {userPromise.pending &&

Loading user data...

} + + {userPromise.fulfilled && ( +
+

{userPromise.result.name}

+

{userPromise.result.email}

+
+ )} +
+ ); + }, + { + props: { + userId: { type: Number, value: () => 1 } + } + } +); + +customElements.define("user-profile", UserProfile); + +// --- PARENT COMPONENT --- +// Orchestrates multiple children and listens to their promise states +export const Dashboard = c( + () => { + // useSuspense listens to all usePromise calls in its child tree + const suspense = useSuspense(); + + return ( + +

Dashboard

+ + {/* The suspense status will be pending if ANY child promise is pending */} + {suspense.pending ? ( +
Global Loading...
+ ) : ( +
All data loaded!
+ )} + + {/* We pass the constructor instances directly in JSX */} +
+ + +
+
+ ); + }, + { + styles: css` + :host { display: block; font-family: sans-serif; } + .spinner { color: blue; } + ` + } +); + +customElements.define("app-dashboard", Dashboard); +``` diff --git a/.agents/skills/atomico/examples/4-context.md b/.agents/skills/atomico/examples/4-context.md new file mode 100644 index 0000000..b946f4d --- /dev/null +++ b/.agents/skills/atomico/examples/4-context.md @@ -0,0 +1,55 @@ +# Example: Context API + +Using `createContext` and `useContext` to share state without prop drilling. + +> [!IMPORTANT] +> In Atomico, context values **must always be objects**. Do not use primitive values (strings, numbers, booleans) directly as the context value, because Atomico relies on object references for reactive updates across the DOM tree. + +```tsx +import { c, createContext, useContext, useProp } from "atomico"; + +// 1. Create the Context with a default object +export const ThemeContext = createContext({ theme: "light" }); + +// 2. Create the Provider Component +export const ThemeProvider = c( + ({ theme }) => { + // useProp to expose the value attribute, so parents can change the theme + const [currentTheme] = useProp("theme"); + + return ( + + {/* Provide the object to the context */} + + + + + ); + }, + { + props: { + theme: { type: String, value: () => "light" } + } + } +); + +customElements.define("theme-provider", ThemeProvider); + +// 3. Create a Consumer Component +export const ThemeConsumer = c( + () => { + // Consume the context object + const ctx = useContext(ThemeContext); + + return ( + +
+ Current Theme: {ctx.theme} +
+
+ ); + } +); + +customElements.define("theme-consumer", ThemeConsumer); +``` diff --git a/.agents/skills/atomico/examples/5-slot-slider.md b/.agents/skills/atomico/examples/5-slot-slider.md new file mode 100644 index 0000000..eb98560 --- /dev/null +++ b/.agents/skills/atomico/examples/5-slot-slider.md @@ -0,0 +1,58 @@ +# Example: Manual Slot Assignment (Slider) + +Using `useNodes` and `assignNode` on slots to dynamically distribute Light DOM children to specific slots based on user interaction (e.g., a slider or carousel). + +> [!IMPORTANT] +> When assigning slots manually (`slotAssignment: "manual"`), you must observe the Light DOM children using `useNodes` (not `useSlot`, since they are not assigned yet) and then render a `` to map them into the Shadow DOM. + +```tsx +import { c, css, useNodes, useProp } from "atomico"; + +export const Slider = c( + () => { + // useNodes tracks the light DOM children (filtering out text/comments if needed) + const nodes = useNodes((el) => el instanceof Element); + const [currentIndex, setCurrentIndex] = useProp("index"); + + const next = () => setCurrentIndex((currentIndex + 1) % (nodes.length || 1)); + const prev = () => setCurrentIndex((currentIndex - 1 + nodes.length) % (nodes.length || 1)); + + // Get the active node based on the current index + const activeNode = nodes[currentIndex] || nodes[0]; + + return ( + // Required config for manual slot assignment + +
+ + +
+ {/* + We use the assignNode prop on the slot to explicitly + assign the activeNode into this slot + */} + {activeNode && } +
+ + +
+
+ ); + }, + { + props: { + index: { type: Number, value: () => 0, reflect: true } + }, + styles: css` + :host { display: block; } + .slider-container { + display: flex; + align-items: center; + gap: 1rem; + } + ` + } +); + +customElements.define("ui-slider", Slider); +``` diff --git a/.agents/skills/atomico/examples/6-other-hooks.md b/.agents/skills/atomico/examples/6-other-hooks.md new file mode 100644 index 0000000..9bba074 --- /dev/null +++ b/.agents/skills/atomico/examples/6-other-hooks.md @@ -0,0 +1,61 @@ +# Example: Other Hooks (useId, useHost, useUpdate) + +An example combining utility hooks like `useId` (for accessibility), `useHost` (for DOM manipulation), and `useUpdate`. + +```tsx +import { c, css, useId, useHost, useUpdate, useEffect } from "atomico"; + +export const Tooltip = c( + () => { + // 1. useId: Generates a unique ID safe for accessibility attributes + const tooltipId = useId(); + + // 2. useHost: Returns a reference to the Custom Element () instance + const host = useHost(); + + // 3. useUpdate: Returns a function to manually trigger a re-render + const update = useUpdate(); + + useEffect(() => { + // Using useHost to listen to native DOM events directly on the element + const el = host.current; + const onResize = () => { + console.log("Resized!"); + update(); // Manually update component + }; + + window.addEventListener("resize", onResize); + return () => window.removeEventListener("resize", onResize); + }, []); + + return ( + + {/* Using the unique ID for aria properties */} + + + + ); + }, + { + styles: css` + :host { display: inline-block; position: relative; } + [role="tooltip"] { + display: none; + position: absolute; + top: 100%; + background: black; + color: white; + } + button:hover + [role="tooltip"] { + display: block; + } + ` + } +); + +customElements.define("ui-tooltip", Tooltip); +``` diff --git a/.agents/skills/atomico/rules/component-creation.md b/.agents/skills/atomico/rules/component-creation.md new file mode 100644 index 0000000..0b98fea --- /dev/null +++ b/.agents/skills/atomico/rules/component-creation.md @@ -0,0 +1,80 @@ +# Component Creation + +Properly initializing an Atomico component is critical for types, instances, and Virtual DOM rendering. + +## `c(render, config)` Function and Export + +**Always wrap your component in the `c()` function using the inline `c(render, config)` signature, and export the generated instance.** + +When using JSX, you must use the exported instance (e.g. ` + + ); + } +); +// ❌ BAD: State is completely hidden from the outside HTML +``` + +### ✅ Correct + +```tsx +import { c, useProp } from "atomico"; + +export const Toggle = c( + () => { + // ✅ GOOD: Using useProp linked to the "active" property + const [active, setActive] = useProp("active"); + + return ( + + + + ); + }, + { + props: { + active: { + type: Boolean, + reflect: true, // State changes will reflect as the [active] HTML attribute + value: () => false + } + } + } +); +``` diff --git a/.agents/skills/atomico/rules/styling-application.md b/.agents/skills/atomico/rules/styling-application.md new file mode 100644 index 0000000..a66c5e4 --- /dev/null +++ b/.agents/skills/atomico/rules/styling-application.md @@ -0,0 +1,108 @@ +# Styling Application + +Atomico uses the `css` tagged template literal to parse and cache styles efficiently using Constructable Stylesheets. + +> [!IMPORTANT] +> **Requirement**: To use the `styles` configuration parameter in Atomico, your component MUST return `` as the root element. Styles are scoped entirely to the Shadow DOM. + +## CSS Template Literal + +**Always use the `css` tagged template literal** to define component styles inside the inline configuration object. Do not pass a raw string. + +## Decoupling Logic from JSX (The 100% Pattern) + +Atomico strongly encourages abstracting representation entirely into the CSS via Shadow DOM, rather than interpolating strings for classes in the JSX. + +Use CSS Custom Properties (variables) and `:host([attribute])` selectors to change internal styles based on the component's public state, instead of adding logic to the JSX. + +### ❌ Suboptimal (80% Pattern - Avoid) + +Coupling the logic into the JSX using dynamic classes. + +```tsx +import { c, css, event } from "atomico"; + +export const AppButton = c( + ({ variant, clickButton }) => { + return ( + + {/* ❌ Suboptimal: Logic mixed into JSX via class interpolation */} + + + ); + }, + { + props: { + variant: { type: String, value: () => "primary", reflect: true }, + clickButton: event({ bubbles: true, composed: true }), + }, + styles: css` + :host { display: inline-block; } + .btn { /* Base styles */ } + .primary { background-color: #007bff; color: white; } + .secondary { background-color: #6c757d; color: white; } + ` + } +); +``` + +### ✅ Correct (100% Pattern) + +Clean JSX. Logic is handled purely by CSS variables mapped to the `:host` state. + +```tsx +import { c, css, event } from "atomico"; + +export const AppButton = c( + ({ clickButton }) => { + return ( + + {/* ✅ GOOD: Clean JSX. Styling is decoupled. */} + + + ); + }, + { + props: { + variant: { type: String, value: () => "primary", reflect: true }, + clickButton: event({ bubbles: true, composed: true }), + }, + styles: css` + :host { + display: inline-block; + --button-color: black; + --button-bgcolor: grey; + } + /* Variants update CSS variables */ + :host([variant="primary"]) { + --button-bgcolor: #007bff; + --button-color: white; + } + :host([variant="secondary"]) { + --button-bgcolor: #6c757d; + --button-color: white; + } + .btn { + padding: 0.5rem 1rem; + border: none; + border-radius: 0.25rem; + cursor: pointer; + font-size: 1rem; + transition: opacity 0.2s; + /* The button simply consumes the variables */ + background-color: var(--button-bgcolor); + color: var(--button-color); + } + .btn:hover { + opacity: 0.8; + } + ` + } +); + +customElements.define("app-button", AppButton); +``` diff --git a/developer-experience/skills-evaluator/eval.js b/developer-experience/skills-evaluator/eval.js new file mode 100644 index 0000000..3651438 --- /dev/null +++ b/developer-experience/skills-evaluator/eval.js @@ -0,0 +1,117 @@ +import { execSync } from "node:child_process"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const promptsPath = path.join(__dirname, "prompts.json"); +const sandboxDir = path.join(__dirname, "sandbox"); +const prompts = JSON.parse(fs.readFileSync(promptsPath, "utf8")); + +if (!fs.existsSync(sandboxDir)) { + fs.mkdirSync(sandboxDir); +} + +console.log("Iniciando evaluación de Skills de Atomico en modo Sandbox...\n"); + +for (const p of prompts) { + const outFile = path.join(sandboxDir, `${p.id}.tsx`); + // Le pedimos explícitamente a la IA que retorne el código + const fullPrompt = `${p.prompt} Solo retorna el código en un bloque markdown de typescript.`; + + console.log(`=================================================`); + console.log(`Evaluando prompt [${p.id}]`); + console.log(`-------------------------------------------------`); + + let attempt = 1; + const maxAttempts = 3; + let percentage = 0; + + while (attempt <= maxAttempts && percentage < 90) { + console.log(`\nIntento ${attempt}...`); + try { + // Ejecutamos gemini cli + // Agregamos timeout alto por la latencia esperada + const output = execSync( + `gemini prompt "${fullPrompt.replace(/"/g, '\\"')}"`, + { + encoding: "utf8", + stdio: ["pipe", "pipe", "ignore"], + timeout: 120000 + } + ); + + // Guardamos el output en la carpeta sandbox (No se elimina, sirve para debug) + // Si hay iteraciones, sobreescribe con la versión más reciente + fs.writeFileSync(outFile, output, "utf8"); + + let score = 0; + let checks = []; + + const hasHost = output.includes(" como raíz", pass: hasHost }); + if (hasHost) score++; + + const avoidsUseState = p.prompt.includes("no uses useState") + ? !output.includes("useState(") + : true; + const usesUseProp = + output.includes("useProp(") || !output.includes("useState("); + const stateScorePass = avoidsUseState && usesUseProp; + checks.push({ + name: "Uso correcto de estados (useProp preferido o evita useState)", + pass: stateScorePass + }); + if (stateScorePass) score++; + + const hasCss = output.includes("css`"); + checks.push({ + name: "Declara estilos con literal template css`...`", + pass: hasCss + }); + if (hasCss) score++; + + const hasC = output.includes("c("); + checks.push({ + name: "Usa la función constructora c() en formato inline", + pass: hasC + }); + if (hasC) score++; + + const isExported = + output.includes("export const ") || + output.includes("export function "); + checks.push({ + name: "Exporta la instancia del componente", + pass: isExported + }); + if (isExported) score++; + + percentage = (score / 5) * 100; + + console.log( + `Resultados de Evaluación: ${percentage}% de precisión` + ); + checks.forEach((c) => + console.log(` [${c.pass ? "✓" : "x"}] ${c.name}`) + ); + + if (percentage >= 90) { + console.log( + `✅ ¡Éxito en el intento ${attempt}! Componente guardado en sandbox/${p.id}.md` + ); + break; + } else { + console.log( + `⚠️ Falló el umbral del 90%. El agente no aplicó todas las reglas. Iterando...` + ); + attempt++; + } + } catch (e) { + console.error(`Error en intento ${attempt}:`, e.message); + attempt++; + } + } +} diff --git a/developer-experience/skills-evaluator/prompts.json b/developer-experience/skills-evaluator/prompts.json new file mode 100644 index 0000000..977f849 --- /dev/null +++ b/developer-experience/skills-evaluator/prompts.json @@ -0,0 +1,14 @@ +[ + { + "id": "basic-component", + "prompt": "Crea un componente de boton que exponga un evento al hacer click y reciba una variante por prop." + }, + { + "id": "stateful-input", + "prompt": "Crea un componente de input customizado que refleje su valor como atributo HTML y utilice un shadowDom. No uses el hook useState, debe estar sincronizado hacia afuera." + }, + { + "id": "generic-ui", + "prompt": "Necesito crear una vista para una tarjeta de perfil de usuario. Debe recibir 'name' y 'role' y tener algo de css." + } +] From fa0f50f35688560fd16e7df8d36b5ac8290cf883 Mon Sep 17 00:00:00 2001 From: Matias Trujillo Date: Sat, 30 May 2026 22:06:37 -0400 Subject: [PATCH 02/17] feat: iteration with agent --- .agents/skills/atomico-app-patterns/SKILL.md | 321 --------------- .agents/skills/atomico-component/SKILL.md | 235 ----------- .agents/skills/atomico-context/SKILL.md | 233 ----------- .agents/skills/atomico-css-styling/SKILL.md | 188 --------- .agents/skills/atomico-hooks-async/SKILL.md | 274 ------------- .agents/skills/atomico-hooks-core/SKILL.md | 227 ----------- .agents/skills/atomico-hooks-dom/SKILL.md | 305 -------------- .agents/skills/atomico-hooks-effects/SKILL.md | 191 --------- .agents/skills/atomico-hooks-events/SKILL.md | 210 ---------- .agents/skills/atomico-hooks-forms/SKILL.md | 337 ---------------- .agents/skills/atomico-hooks-props/SKILL.md | 167 -------- .agents/skills/atomico-jsx-patterns/SKILL.md | 376 ------------------ .agents/skills/atomico-rendering/SKILL.md | 226 ----------- .../atomico/rules/component-creation.md | 37 ++ .agents/skills/atomico/rules/hooks-api.md | 347 ++++++++++++++++ .../skills/atomico/rules/props-declaration.md | 82 ++++ developer-experience/skills-evaluator/eval.js | 4 +- 17 files changed, 468 insertions(+), 3292 deletions(-) delete mode 100644 .agents/skills/atomico-app-patterns/SKILL.md delete mode 100644 .agents/skills/atomico-component/SKILL.md delete mode 100644 .agents/skills/atomico-context/SKILL.md delete mode 100644 .agents/skills/atomico-css-styling/SKILL.md delete mode 100644 .agents/skills/atomico-hooks-async/SKILL.md delete mode 100644 .agents/skills/atomico-hooks-core/SKILL.md delete mode 100644 .agents/skills/atomico-hooks-dom/SKILL.md delete mode 100644 .agents/skills/atomico-hooks-effects/SKILL.md delete mode 100644 .agents/skills/atomico-hooks-events/SKILL.md delete mode 100644 .agents/skills/atomico-hooks-forms/SKILL.md delete mode 100644 .agents/skills/atomico-hooks-props/SKILL.md delete mode 100644 .agents/skills/atomico-jsx-patterns/SKILL.md delete mode 100644 .agents/skills/atomico-rendering/SKILL.md create mode 100644 .agents/skills/atomico/rules/hooks-api.md diff --git a/.agents/skills/atomico-app-patterns/SKILL.md b/.agents/skills/atomico-app-patterns/SKILL.md deleted file mode 100644 index 3a98e05..0000000 --- a/.agents/skills/atomico-app-patterns/SKILL.md +++ /dev/null @@ -1,321 +0,0 @@ ---- -name: atomico-app-patterns -description: > - Full application patterns and component composition in Atomico. Triggers when - the user needs to build a multi-component application, implement parent-child - communication, manage shared state across components, or structure a web - component project. Covers real-world patterns like todo apps, forms, and - component hierarchies. -license: MIT -compatibility: "Atomico >=1.79, TypeScript >=5.0" -metadata: - category: patterns - priority: medium ---- - -# Application Patterns - -## Complete Todo App — Component Composition - -This example demonstrates the key Atomico patterns for building a real -application with multiple communicating components. - -### Component Architecture - -``` -TodoApp (manages state via useProp) -├── TodoForm (dispatches createTask event) -└── TodoTask[] (dispatches changeTask event, reflects checked state) -``` - -### TodoForm — Event-Driven Child - -```tsx -import { c, css, event, useRef } from "atomico"; - -export const TodoForm = c( - ({ createTask }) => { - const ref = useRef(); - return ( - -
{ - event.preventDefault(); - const message = ref.current.value.trim(); - if (message) createTask(message); - event.currentTarget.reset(); - }} - > - - -
-
- ); - }, - { - props: { - createTask: event({ bubbles: true, composed: true }) - }, - styles: css` - button { - background: #9cbeff; - border-radius: 0.5rem; - padding: 0.5rem 1rem; - border: none; - cursor: pointer; - } - input { - padding: 0.5rem; - border: 1px solid #dcdce1; - border-radius: 0.5rem; - margin-right: 1rem; - } - ` - } -); - -customElements.define("todo-form", TodoForm); -``` - -### TodoTask — Stateful Child with Reflection - -```tsx -import { c, css, event } from "atomico"; - -export const TodoTask = c( - ({ checked, message, changeTask }) => ( - - - - ), - { - props: { - changeTask: event({ bubbles: true, composed: true }), - message: String, - checked: { - type: Boolean, - reflect: true // Mirrors to HTML attribute for CSS styling - } - }, - styles: css` - :host { - --background: #f0f0f9; - --border: #dcdce1; - } - :host([checked]) { - --background: #a3ebd4; - --border: #6ee2c9; - } - label { - display: block; - background: var(--background); - border: 1px solid var(--border); - padding: 1rem; - border-radius: 0.5rem; - cursor: pointer; - } - ` - } -); - -customElements.define("todo-task", TodoTask); -``` - -### TodoApp — Parent Orchestrator - -```tsx -import { c, css, useProp } from "atomico"; -import { TodoForm } from "./todo-form.js"; -import { TodoTask } from "./todo-task.js"; - -interface Task { - checked: boolean; - message: string; -} - -const TodoApp = c( - () => { - const [tasks, setTasks] = useProp("task"); - - return ( - - {/* ✅ Constructor instance — full type inference */} - { - setTasks([...tasks, { checked: false, message: detail }]); - }} - /> - {tasks.map((task, index) => ( - { - setTasks( - tasks.map((item, i) => - i === index - ? { ...item, checked: detail } - : item - ) - ); - }} - /> - ))} - - ); - }, - { - props: { - task: { - type: Array, - value: (): Task[] => [ - { checked: true, message: "Sample 1" }, - { checked: false, message: "Sample 2" } - ] - } - }, - styles: css`:host { display: grid; gap: 0.5rem; }` - } -); - -customElements.define("todo-app", TodoApp); -``` - ---- - -## Communication Patterns - -### 1. Parent → Child: Props - -```tsx - -``` - -### 2. Child → Parent: Events (via `event()`) - -```tsx -// Child declares event prop -props: { - changeTask: event({ bubbles: true, composed: true }) -} - -// Child fires event -changeTask(true); - -// Parent listens via on-prefix - handleChange(detail)} /> -``` - -### 3. Cross-Tree: Context - -```tsx -const AppState = createContext({ theme: "light" }); - -// Provider -const App = c(() => { - useProvider(AppState, { theme: "dark" }); - return ; -}); - -// Consumer (any depth) -const DeepChild = c(() => { - const { theme } = useContext(AppState); - return {theme}; -}); -``` - -### 4. Sibling: Via shared parent state - -```tsx -const Parent = c(() => { - const [selected, setSelected] = useState(null); - return ( - - setSelected(detail)} /> - - - ); -}); -``` - ---- - -## Project Structure Recommendation - -``` -my-app/ -├── src/ -│ ├── components/ -│ │ ├── my-button/ -│ │ │ ├── my-button.tsx # Component definition -│ │ │ └── my-button.css.ts # Styles (optional separate file) -│ │ ├── my-input/ -│ │ │ └── my-input.tsx -│ │ └── index.ts # Re-exports -│ ├── contexts/ -│ │ └── app-context.ts # Shared contexts -│ ├── hooks/ -│ │ └── use-api.ts # Custom hooks -│ └── index.ts # App entry, customElements.define -├── package.json -└── tsconfig.json -``` - -### Key Principles - -1. **One component per file** — export the constructor for JSX usage -2. **Register at entry point** — `customElements.define` calls at the app level -3. **Shared styles in separate files** — import and compose in `styles` array -4. **Custom hooks next to components** — or in a shared `hooks/` directory -5. **Contexts as singletons** — define contexts in a `contexts/` directory - ---- - -## TypeScript Configuration - -```json -{ - "compilerOptions": { - "jsx": "react-jsx", - "jsxImportSource": "atomico", - "moduleResolution": "bundler", - "strict": true, - "target": "ESNext", - "module": "ESNext" - } -} -``` - ---- - -## Registration Pattern - -### ✅ Centralized Registration - -```ts -// index.ts — single registration point -import { TodoApp } from "./components/todo-app.js"; -import { TodoForm } from "./components/todo-form.js"; -import { TodoTask } from "./components/todo-task.js"; - -customElements.define("todo-app", TodoApp); -customElements.define("todo-form", TodoForm); -customElements.define("todo-task", TodoTask); -``` - -### ✅ Co-located Registration (also valid) - -```ts -// todo-task.tsx — register alongside definition -export const TodoTask = c(/* ... */); -customElements.define("todo-task", TodoTask); -``` - -Both patterns are valid. Co-located is simpler for small projects; centralized -gives more control over naming and load order. diff --git a/.agents/skills/atomico-component/SKILL.md b/.agents/skills/atomico-component/SKILL.md deleted file mode 100644 index c2d633c..0000000 --- a/.agents/skills/atomico-component/SKILL.md +++ /dev/null @@ -1,235 +0,0 @@ ---- -name: atomico-component -description: > - Create Atomico web components using the `c()` function. Triggers when the user - needs to create a new custom element, define reactive props, attach styles, or - register a component with `customElements.define`. Do NOT trigger for React, - Vue, or Angular component creation. Atomico components are functional web - components that return JSX with a mandatory `` root element. -license: MIT -compatibility: "Atomico >=1.79, TypeScript >=5.0, JSX via atomico/jsx-runtime" -metadata: - category: core - priority: high ---- - -# Atomico Component Creation — `c()` - -## Overview - -`c()` is the core function to create web components in Atomico. It receives a -**render function** and an optional **configuration object** with `props`, -`styles`, and `form`. - -```ts -import { c, css } from "atomico"; - -const MyComponent = c( - (props) => ( - -

{props.message}

-
- ), - { - props: { - message: { type: String, value: () => "Hello" } - }, - styles: css`:host { display: block; }` - } -); - -customElements.define("my-component", MyComponent); -``` - -## Critical Rules - -### 1. Always return `` as root - -Every Atomico component MUST return `` as the outermost JSX element. -`` maps to the custom element itself. - -```tsx -// ✅ Correct -const MyComponent = c(() =>

Hello

); - -// ❌ Wrong — never return a div or fragment as root -const MyComponent = c(() =>
Hello
); -``` - -### 2. Prefer Constructor Instances in JSX - -**Always use the constructor reference** (e.g., ``) instead of the -tag-name string (``) when composing components in JSX. This enables: - -- **Full type inference** for props, events, and methods -- **Instance dependency tracking** — the bundler knows about the relationship -- **Autocompletion** in IDEs for all declared props and events - -```tsx -// ✅ Preferred — types are inferred, props are validated - - -// ⚠️ Avoid — no type inference, no prop validation - -``` - -### 3. Props Declaration Patterns - -Props can be declared in shorthand or detailed form: - -```ts -{ - props: { - // Shorthand — type only, value is undefined until set - name: String, - count: Number, - active: Boolean, - items: Array, - config: Object, - - // Detailed — with default value, reflection, etc. - message: { - type: String, - value: () => "default", // Factory function for default - reflect: true, // Mirrors to HTML attribute - attr: "custom-attr" // Custom attribute name - }, - - // Custom type (e.g., another component constructor) - child: { - type: MyOtherComponent, - value: () => new MyOtherComponent() - }, - - // Promise type with typed return - data: { - type: Promise, - value: async (): Promise => [] - } - } -} -``` - -### 4. Supported Prop Types - -| Type | JS Type | Attribute Parsing | -|------|---------|-------------------| -| `String` | `string` | Direct string | -| `Number` | `number` | `Number(value)` | -| `Boolean` | `boolean` | Presence = `true` | -| `Array` | `any[]` | `JSON.parse` | -| `Object` | `object` | `JSON.parse` | -| `Date` | `Date` | `new Date(value)` | -| `Map` | `Map` | `new Map(value)` | -| `Promise` | `Promise` | N/A | -| `Function` | `function` | N/A | -| Custom Class | instance | `new Type(value)` | -| `null` (Any) | any | No validation | - -### 5. Styles with `css` Tagged Template - -```tsx -import { c, css } from "atomico"; - -const MyComponent = c( - () => ( - - - - ), - { - styles: css` - :host { - display: block; - padding: 1rem; - } - ::slotted(*) { - margin: 0.5rem; - } - ` - } -); -``` - -> **Note**: Styles use `adoptedStyleSheets` and are cached by CSS text. They -> require `shadowDom` to be enabled on ``. - -### 6. Events with `event()` and `callback()` - -```tsx -import { c, event, callback } from "atomico"; - -const MyComponent = c( - (props) => ( - - - - ), - { - props: { - // Dispatches a CustomEvent — listen with `onchange` in JSX - change: event<{ id: number }>({ bubbles: true, composed: true }), - // Function prop — parent provides logic, child invokes it - processMarkdown: callback<() => Promise>() - } - } -); - -// Consuming with JSX (constructor instance): - console.log(detail.id)} - processMarkdown={() => Promise.resolve("# Hello")} -/>; -``` - -### 7. Form-Associated Components - -```tsx -const MyInput = c( - ({ name }) => { - const [value, setValue] = useFormProps(); - return ( - - setValue(currentTarget.value)} - /> - - ); - }, - { - form: true, // Enables formAssociated - props: { - name: String, - value: String - } - } -); -``` - -### 8. Component without Props - -```tsx -const SimpleComponent = c(() =>

No props needed

); -customElements.define("simple-component", SimpleComponent); -``` - -### 9. ShadowDOM Options - -```tsx -// Simple shadowDom -... - -// With options -... -``` - -## Anti-Patterns - -- ❌ Using `document.createElement` instead of JSX constructors -- ❌ Returning anything other than `` from the render function -- ❌ Mutating props directly — props are read-only in the render function -- ❌ Using string tag names in JSX when the constructor is available in scope -- ❌ Defining `value` as a plain value instead of a factory function diff --git a/.agents/skills/atomico-context/SKILL.md b/.agents/skills/atomico-context/SKILL.md deleted file mode 100644 index 64f4808..0000000 --- a/.agents/skills/atomico-context/SKILL.md +++ /dev/null @@ -1,233 +0,0 @@ ---- -name: atomico-context -description: > - Use Atomico's context system: createContext, useContext, useProvider. - Triggers when the user needs to share state or dependencies across nested web - components without prop-drilling, implement theme providers, or pass - configuration down a component tree. Works across shadow DOM boundaries via - events, not React-style fiber context. -license: MIT -compatibility: "Atomico >=1.79" -metadata: - category: core - priority: medium ---- - -# Context System - -Atomico's context system works across shadow DOM boundaries using Custom Events -for communication. It is conceptually similar to React Context but designed -for web component composition. - -## `createContext` — Define a Context - -Creates a context object with a default value. The context itself is a web -component that wraps content and provides values to descendants. - -### API - -```ts -const MyContext = createContext(defaultValue: T); -``` - -### Usage - -```tsx -import { c, createContext, useContext, css } from "atomico"; - -// Create context with default value -const ThemeContext = createContext({ name: "Atomico" }); - -// Register the context as a custom element -customElements.define("theme-context", ThemeContext); -``` - ---- - -## `useContext` — Consume a Context Value - -Reads the current value from the nearest ancestor context provider. - -### API - -```ts -const value = useContext(ContextConstructor); -``` - -### Basic Example - -```tsx -import { c, createContext, useContext, css } from "atomico"; - -const MyContext = createContext({ name: "Atomico" }); - -const MyComponent = c( - () => { - const { name } = useContext(MyContext); - - return ( - - {name} - - ); - }, - { - styles: css`:host { display: grid; }` - } -); - -customElements.define("my-context", MyContext); -customElements.define("my-component", MyComponent); -``` - -### HTML Usage - -```html - - - -``` - ---- - -## `useProvider` — Provide a Context Value - -Provides a value for a context to all descendants. This is the lower-level API -used internally by `createContext`. - -### API - -```ts -useProvider(ContextConstructor, value); -``` - -### Custom Provider Pattern - -```tsx -const AppState = createContext({ count: 0, theme: "dark" }); - -const AppProvider = c( - () => { - const [count, setCount] = useState(0); - - useProvider(AppState, { - count, - increment: () => setCount((c) => c + 1) - }); - - return ( - - - - ); - } -); -``` - ---- - -## Nested Context and Value Override - -Contexts can be nested to override values at different tree levels: - -```tsx -const MyContext = createContext({ name: "Atomico" }); - -const MyComponent = c( - () => { - const { name } = useContext(MyContext); - - return ( - - {name} - -
- Dynamic Slot - {/* Override context for slotted children */} - - - -
- -
- Static Slot - - - -
-
- ); - }, - { - styles: css` - :host { display: grid; } - div { - padding: 0 1rem; - border-left: 1px solid red; - } - ` - } -); -``` - -### HTML for Nested Example - -```html - - - - - - - - - - -``` - ---- - -## How It Works Internally - -1. **Provider** listens for `ConnectContext` events (bubbles + composed) -2. **Consumer** dispatches `ConnectContext` with context ID and a connect callback -3. Provider intercepts the event, stops propagation, and calls the connect callback -4. Provider dispatches `ChangedValue` events when the value updates -5. Consumer listens for `ChangedValue` on the provider element and re-renders - -This event-based mechanism transparently crosses shadow DOM boundaries. - ---- - -## Best Practices - -### ✅ Register contexts as custom elements - -```ts -customElements.define("my-context", MyContext); -``` - -### ✅ Use constructor instances in JSX (for type safety) - -```tsx - - - -``` - -### ✅ Default values as fallback - -If no provider is found, `useContext` falls back to the default value passed -to `createContext`. - -### ❌ Don't mutate context values - -Always provide new object references to trigger updates: - -```tsx -// ❌ Mutation doesn't trigger update -context.name = "New"; - -// ✅ New object reference triggers update -useProvider(MyContext, { ...context, name: "New" }); -``` diff --git a/.agents/skills/atomico-css-styling/SKILL.md b/.agents/skills/atomico-css-styling/SKILL.md deleted file mode 100644 index c175cd7..0000000 --- a/.agents/skills/atomico-css-styling/SKILL.md +++ /dev/null @@ -1,188 +0,0 @@ ---- -name: atomico-css-styling -description: > - Use the css tagged template literal and adoptedStyleSheets for styling Atomico - components. Triggers when the user needs to style web components, use CSS - custom properties, create themeable components, or understand how Atomico - manages stylesheets. Do NOT trigger for external CSS frameworks unless - combined with Atomico's css utility. -license: MIT -compatibility: "Atomico >=1.79, browsers with adoptedStyleSheets support" -metadata: - category: core - priority: medium ---- - -# CSS & Styling - -## `css` Tagged Template - -Creates and caches `CSSStyleSheet` objects using the browser's -`adoptedStyleSheets` API for optimal performance. - -### Basic Usage - -```tsx -import { c, css } from "atomico"; - -const MyComponent = c( - () => ( - -

Styled component

-
- ), - { - styles: css` - :host { - display: block; - padding: 1rem; - font-family: system-ui; - } - h1 { - color: #333; - margin: 0; - } - ` - } -); -``` - -### How It Works - -1. `css` receives a template literal and produces a `CSSStyleSheet` -2. Sheets are cached by CSS text — identical styles share one instance -3. Sheets are applied to the component's `shadowRoot.adoptedStyleSheets` -4. Styles are only applied after the first non-suspended render - -### Interpolation - -```tsx -const primaryColor = "#3b82f6"; - -const styles = css` - :host { - --primary: ${primaryColor}; - color: var(--primary); - } -`; -``` - -### Multiple Style Sheets - -The `styles` option can be an array for composing shared styles: - -```tsx -const baseStyles = css` - :host { display: block; box-sizing: border-box; } -`; - -const themeStyles = css` - :host { --bg: #fff; --fg: #111; } -`; - -const MyComponent = c( - () => content, - { - styles: [baseStyles, themeStyles, css` - h1 { color: var(--fg); } - `] - } -); -``` - ---- - -## CSS Custom Properties (Theming) - -Custom properties pierce shadow DOM boundaries, making them the primary -mechanism for theming Atomico components. - -### Define in Component - -```tsx -const MyCard = c( - () => , - { - styles: css` - :host { - --card-bg: #f0f0f9; - --card-border: #dcdce1; - --card-radius: 0.5rem; - } - :host([active]) { - --card-bg: #a3ebd4; - --card-border: #6ee2c9; - } - :host { - background: var(--card-bg); - border: 1px solid var(--card-border); - border-radius: var(--card-radius); - padding: 1rem; - } - ` - } -); -``` - -### Override from Outside - -```css -/* Page CSS can override custom properties */ -my-card { - --card-bg: #1a1a2e; - --card-border: #16213e; -} -``` - ---- - -## Common Patterns - -### `:host` Selector - -```css -/* Default state */ -:host { display: block; } - -/* Attribute-based state */ -:host([active]) { background: green; } -:host([disabled]) { opacity: 0.5; pointer-events: none; } - -/* Context-based state */ -:host(:hover) { transform: scale(1.02); } -:host(:focus-within) { outline: 2px solid blue; } -``` - -### `::slotted` Pseudo-Element - -```css -/* Style slotted children (one level deep) */ -::slotted(*) { margin: 0.5rem; } -::slotted(h1) { font-size: 2rem; } -::slotted([slot="header"]) { font-weight: bold; } -``` - -### `:host-context` (Limited Support) - -```css -/* Style based on ancestor */ -:host-context(.dark-theme) { - --bg: #1a1a2e; -} -``` - ---- - -## ⚠️ Important Notes - -1. **Shadow DOM required**: `css` and `styles` only work with `shadowDom` - enabled. Without shadow DOM, use regular page CSS. - -2. **CSSStyleSheet caching**: Identical CSS text produces the same sheet - instance. This is memory-efficient for shared base styles. - -3. **No `` tags inside the JSX ``. -- **ALWAYS** use the `css` tagged template literal from `'atomico'` and pass it to the `styles` property in the component configuration object: `c(Render, { styles: css`...` })`. Review the styling skill. - -## 🛑 Rule 3: `c()` Execution Context -Atomico Web Components are functional but map directly to Custom Elements. -- The root of an Atomico component JSX MUST always be the `` tag, which allows modifying the physical bounds of the Web Component. -- Hooks (`useState`, `useEffect`, etc.) in Atomico map to the Web Component `connectedCallback`/`disconnectedCallback` lifecycle, not a fiber tree. - -## 🛑 Rule 4: Registration -Every custom element must be explicitly defined using the standard browser API: -`customElements.define("my-element", MyElementConstructor);` - -## 🛑 Rule 5: Event Naming and `on` Prefix -Atomico's JSX engine automatically prepends `on` to event listeners. -- **NEVER** use the `on` prefix when declaring event properties in the `props` object. -- ❌ Bad: `props: { onSearch: event() }` (Creates an `ononSearch` listener) -- ✅ Good: `props: { search: event() }` (Used in JSX as ``) - -## 🛑 Rule 6: `useProp` over `event()` for Observable State -If an event is dispatching a value that reflects the state of the component (like an input's value, a boolean toggle, or a search query), **do not use an isolated `event()` dispatcher**. -Instead, configure a regular prop and use `useProp("propName")`. -- Using `useProp` gives you a setter. When you call the setter (`setSearch("new query")`), Atomico automatically updates the DOM property on the custom element instance AND dispatches the property observer event natively. -- This allows consumers using the constructor instance to access the value directly from the event target (`onSearch={({currentTarget}) => console.log(currentTarget.search)}`) with full IDE type completion. - -## 🛑 Rule 7: Inline JSX Event Handlers over Extracted Functions -Unless a handler is shared across multiple different tags, **NEVER extract it to a standalone function** (like `const handleInput = (e: InputEvent) => ...`). -- **ALWAYS keep it inline** directly in the JSX (`oninput={({currentTarget}) => ...}`). -- Atomico is deeply integrated with TypeScript and JSX. By keeping it inline, TypeScript automatically infers the event type and the `currentTarget` from the DOM tag, completely eliminating the need for manual type casting or verbose typing maintenance. - ---- - -**AGENT ACKNOWLEDGEMENT:** If you are an AI reading this file, your next step is to identify the precise `.agents/skills/` required for the user's prompt, open them via your tools, and apply those specialized concepts strictly to your code output. diff --git a/.agents/skills/atomico-app/SKILL.md b/.agents/skills/atomico-app/SKILL.md new file mode 100644 index 0000000..9fdd054 --- /dev/null +++ b/.agents/skills/atomico-app/SKILL.md @@ -0,0 +1,54 @@ +--- +name: atomico-app +description: > + Specialized skill for managing application architecture, complex views, modular folder structure, and component abstraction in Atomico. + Triggers when the user asks to "create app", "create application", "dashboard", "complex view", "modular architecture", "abstrae", or "reutiliza". +license: MIT +compatibility: "Atomico >=2.0, TypeScript >=5.0" +metadata: + category: app-architecture + priority: high +--- + +# Atomico Application Architecture & Abstraction Guide + +Specialized reference for designing high-performance modular views, managing component trees, and ensuring maximum code reuse in Atomico applications. + +## 1. Search-and-Variant-First Paradigm (Component Reuse) + +Before writing any new component file or Custom Element: +1. **Workspace Inspection**: Search the workspace directories (specifically `components/`) for elements with a similar goal. +2. **Reusability / Variant Check**: + - If a similar component exists, you MUST propose or automatically create a **variation** (e.g., adding a styling variant like `variant="secondary"` or extending its properties) instead of duplicating logic or files. + - If the user/context allows automatic actions, execute it as a variant. If not, explicitly ask the user if they prefer a variation. + +--- + +## 2. Automated View Abstraction (Modularity by Default) + +When tasked with creating a complex view, form, dashboard, or layout: +1. **Never Monolith**: Do NOT write a giant monolithic custom element file containing multiple nested tags or definitions. +2. **Subcomponent Separation**: First, analyze the interface and break it down into clean, single-responsibility subcomponents (e.g. input fields, headers, cards, buttons). +3. **Dedicated Directory**: Place these subcomponents in a dedicated `components/` subfolder (e.g. `components/ui-input.tsx`). +4. **Assembly**: Import the subcomponent constructors and compose them in the main view file (e.g. `todo-app.tsx`). + +--- + +## 3. Specialized State & Tree Traversing Hooks + +Use specialized Atomico hooks to manage modular tree communications: + +* **`useSlot(ref, filter?)`**: Tracks slotted children assigned inside a `` (ideal for compound components like lists or sliders). +* **`useParent(target, cross?)`**: Traverses ancestor elements. Set `cross=true` to cross Shadow DOM boundaries (ideal for child components searching for a parent form or context provider). +* **`usePromise(cb, deps?)`**: Manually tracks async states (result, pending, fulfilled, rejected) cleanly in the component. + +```tsx +import { c, useRef, useSlot, useParent } from "atomico"; + +export const UiListItem = c(() => { + // Traverse parent list element across shadow boundaries + const parentList = useParent("ui-list", true); + + return
  • ; +}); +``` diff --git a/.agents/skills/atomico-design-system/SKILL.md b/.agents/skills/atomico-design-system/SKILL.md new file mode 100644 index 0000000..176c1c3 --- /dev/null +++ b/.agents/skills/atomico-design-system/SKILL.md @@ -0,0 +1,111 @@ +--- +name: atomico-design-system +description: > + Specialized skill for creating design systems, UI kits, form-associated elements, and themes in Atomico. + Triggers when the user asks for "design system", "ui kit", "form-associated", "components index", "theme", or "design tokens". +license: MIT +compatibility: "Atomico >=2.0, TypeScript >=5.0" +metadata: + category: style-form + priority: high +--- + +# Atomico Design System & Form Association Guide + +Specialized reference for building modular, themeable UI components and form-friendly Custom Elements in Atomico. + +## 1. Agnostic Registration & Centralized Index + +Component declaration files (e.g. `my-button.tsx`) MUST only declare and export the component instance, keeping it agnostic of global registry naming conflicts. + +* **Agnostic Exports**: Only export the generated `c(render, config)` instance. Do NOT call `customElements.define` inside the component file. +* **Centralized Index**: Register all custom elements inside a central index file (`components/index.ts`) that imports the instances and defines them globally. + +### Component File (`components/ui-button.tsx`) +```tsx +import { c, css } from "atomico"; + +// Export ONLY the instance +export const UiButton = c( + () => , + { + props: { variant: { type: String, value: () => "primary", reflect: true } } + } +); +``` + +### Central Index File (`components/index.ts`) +```typescript +import { UiButton } from "./ui-button.js"; + +// Centralized DOM custom element registration +customElements.define("ui-button", UiButton); +``` + +--- + +## 2. Form-Friendly Custom Buttons (Form-Associated Buttons) + +A standard ` +
    + ); + }, + { + form: true, // 👈 Required for ElementInternals + props: { + variant: { type: String, value: () => "primary", reflect: true } + } + } +); +``` + +--- + +## 3. Core Design System & CSS Variables + +* **Styling Reflectance**: Use `reflect: true` exclusively when the objective is to control visual states or styling variants via CSS selectors (e.g. `:host([disabled])` or `:host([show])`). +* **Design Tokens**: Style components using CSS Custom Properties (CSS variables) to allow clean external overrides and themes. + +```tsx +export const AlertBox = c( + () => Alert, + { + props: { + show: { type: Boolean, value: () => false, reflect: true } + }, + styles: css` + :host { + display: none; + background-color: var(--alert-bg, #f3f4f6); + } + :host([show]) { + display: block; + } + ` + } +); +``` diff --git a/.agents/skills/atomico/rules/styling-application.md b/.agents/skills/atomico-design-system/rules/styling-application.md similarity index 84% rename from .agents/skills/atomico/rules/styling-application.md rename to .agents/skills/atomico-design-system/rules/styling-application.md index f795c75..4cdae57 100644 --- a/.agents/skills/atomico/rules/styling-application.md +++ b/.agents/skills/atomico-design-system/rules/styling-application.md @@ -1,21 +1,21 @@ # Rule: Styling Application & The 100% Pattern -Directives for applying scoped shadow styles efficiently using Constructable Stylesheets. +Directives for applying scoped shadow styles efficiently using Constructable Stylesheets and host-level attributes. --- ## Directives -1. **Shadow DOM Constraint**: To use the `styles` parameter inside `c()`, the component **MUST** return `` as the root element. Styles are scoped entirely to the Shadow DOM. +1. **Shadow DOM Scoping**: To use the `styles` parameter inside `c()`, the component **MUST** return `` as the root element. Styles are scoped entirely to the Shadow DOM. 2. **Tagged Template Literal**: Always define component styles using the `css` tagged template literal inside the configuration object. Never pass a raw string. 3. **The 100% Pattern (Decoupling Presentation Logic from JSX)**: * **Rule**: Do NOT interpolate class names or inline styles dynamically inside the JSX based on component props/states. - * **Solution**: Keep JSX clean. Use CSS Custom Properties (variables) on `:host` and mapping states using `:host([attribute])` selectors to update presentation. + * **Solution**: Keep JSX clean. Use CSS Custom Properties (variables) on `:host` and map reflected states using `:host([attribute])` selectors to update presentation. ```tsx import { c, css } from "atomico"; -// ✅ CORRECT: 100% Pattern (Clean JSX, styles decoupled using host attributes and variables) +// ✅ CORRECT: 100% Pattern (Clean JSX, styles decoupled using host attributes and CSS variables) export const AppButton = c( () => ( @@ -53,8 +53,6 @@ export const AppButton = c( ` } ); - -customElements.define("app-button", AppButton); ``` ```tsx diff --git a/.agents/skills/atomico/SKILL.md b/.agents/skills/atomico/SKILL.md index 8fe463e..cf257f8 100644 --- a/.agents/skills/atomico/SKILL.md +++ b/.agents/skills/atomico/SKILL.md @@ -52,8 +52,8 @@ export const MyCounter = c( { /** @see rules/props-declaration.md - Props schema definition */ props: { - message: String, - counter: { type: Number, value: () => 0, reflect: true } + message: String, // Shorthand for read-only props without default states or reflection + counter: { type: Number, value: () => 0, reflect: true } // Config object with arrow-function default factory }, styles: css` :host { display: block; color: var(--color-base, blue); } @@ -61,25 +61,30 @@ export const MyCounter = c( ` } ); - -/** @rule avoid-duplicate-registration - Always define components directly without conditional wrappers in source files */ -customElements.define("my-counter", MyCounter); ``` -## 2. Core Validation Rules (Checklist) +## 2. Core Validation Rules (Checklist for LLM Agents) + +1. **Search-and-Variant-First (Component Reuse)**: Before creating any new component file or custom element, search the workspace for elements with a similar goal. If one is found: + - Propose or automatically create a **variation** (e.g. via a `variant` prop or custom styling) instead of duplicating logic or files. + - If the user/context allows automatic actions, execute it as a variation. If not, ask the user. +2. **Agnostic Registration**: Component declaration files (e.g. `my-button.tsx`) MUST only export the component instance. Do NOT call `customElements.define` inside the component file! Centralize all registration inside a components index file (`components/index.ts`) that imports the instances and registers them. This avoids naming collisions and grants consumer flexibility. +3. **Unified Property Factories**: When a property requires a default state, you MUST declare it using the configuration object and an arrow-function factory callback: `value: () => defaultValue`. Declaring raw static values (e.g., `value: ""` or `value: 0`) is incorrect. For simple read-only props without defaults or attribute reflection, use the simple shorthand type directly (e.g. `message: String`) to keep the code clean. +4. **Ref Typeof Inference**: For DOM references in hooks (like `useRef`), type the ref using the component constructor's native typeof: `useRef()`. Do NOT use `any` or `InstanceType`. +5. **Root Element ``**: Every render function MUST return a single `` root element. Returning a `
    `, ``, or other tag as the root is a fatal error. +6. **State Management**: Use `useProp` for values bound to properties or attributes that are accessible from the outside. Use `useState` strictly for internal/private ephemeral state. +7. **Event Handler Casing**: Use strictly lowercase attributes for JSX event bindings (e.g., `onclick={...}`, `onchange={...}`). Do not use React-style camelCase (`onClick`). +8. **Prop Reflection for CSS Styling**: Use `reflect: true` exclusively when the objective is to control component styling and visual states using attribute-based CSS selectors on the host element (e.g. `:host([show]) { ... }` or `:host([disabled]) { ... }`). Prop reflection is restricted to simple serializable types (`String`, `Number`, `Boolean`); never reflect `Object` or `Array` types. +9. **JSX Composition**: Always compose child components using their exported constructor instances (e.g. ``) rather than string tag names (e.g. ``) to inherit full TypeScript typings. +10. **Form-Friendly Custom Buttons**: In HTML forms, custom buttons inside Shadow DOM do not trigger parent form submits natively. If a custom button component is placed inside a `` and needs to act as a submit trigger, it MUST use `form: true` in its configuration, obtain `ElementInternals` via `useInternals()`, and call `internals.form?.requestSubmit()` inside the click handler to delegate submission natively. Never stop click event propagation without handling form submission. +11. **Strict TypeScript Typing (Arrays & Objects)**: To prevent complex properties (such as `Array` or `Object`) from resolving to `never[]` or `{}` in TSX, you MUST use an explicit type assertion (e.g. `as Option[]` or `as Config`) inside the arrow-function default factory callback: `value: () => [] as Option[]`. Raw empty factories are strictly prohibited. -1. **Avoid Local Duplicates**: Before writing any file or custom element, query the workspace to ensure neither the file name (e.g. `tooltip.tsx`) nor the custom element tag name (e.g. `ui-tooltip`) is already occupied. Reuse existing elements or select a unique non-conflicting name. -2. **Root Element ``**: Every render function MUST return a single `` root element. Returning a `
    `, ``, or other tag as the root is a fatal error. -3. **State Management**: Use `useProp` for values bound to properties or attributes that are accessible from the outside. Use `useState` strictly for internal/private ephemeral state. -4. **Event Handler Casing**: Use strictly lowercase attributes for JSX event bindings (e.g., `onclick={...}`, `onchange={...}`). Do not use React-style camelCase (`onClick`). -5. **Prop Reflection Restrictions**: Setting `reflect: true` is allowed exclusively for simple serializable types (`String`, `Number`, `Boolean`). Do not reflect `Object` or `Array` types. -6. **JSX Composition**: Always compose child components using their exported constructor instances (e.g. ``) rather than string tag names (e.g. ``) to inherit full TypeScript typings. ## 3. Directory Index -- `rules/component-creation.md`: The `c()` function, JSX ``, and exporting. +- `rules/component-creation.md`: Component declaration, agnostic export, and central index registration. - `rules/jsx-patterns.md`: Using Constructors vs string tags. -- `rules/props-declaration.md`: Types, `reflect: true`, default factories, events, and callbacks. +- `rules/props-declaration.md`: Unified default factories, strict typeof refs, events, and callbacks. - `rules/styling-application.md`: `` and CSS variables. - `rules/state-management.md`: `useProp` vs `useState`. - `examples/`: Reference implementations (Todo list, async suspense, slots, context, forms, DOM, abort controller). @@ -135,21 +140,20 @@ To match premium quality standards, every AI Agent MUST follow this validation p ```mermaid graph TD - A[1. Plan: Map props, CSS & events] --> B[2. Analyze: Query duplicate names] - B --> C[3. Build: Apply JSDoc & rules] - C --> D[4. Verify: Run compiler & sandbox check] + A[1. Plan: Search existing elements & variants] --> B[2. Analyze: Determine modular structure] + B --> C[3. Build: Apply unified factories & JSDoc] + C --> D[4. Verify: Typeof refs, index registration & TSX Type Check] D -- Fail diagnostics --> E[5. Refactor: Self-correct code] E --> D D -- Pass diagnostics --> F[6. Deliver component] ``` ### Steps Description: -1. **Plan**: Draft the component shape (input props, reflected attributes, custom event types, internal styling). -2. **Analyze**: Check the workspace files and Custom Element tags to prevent naming collisions. -3. **Build**: Code the custom element strictly following the [Core Validation Rules Checklist](#2-core-validation-rules-checklist) and reference rules via inline JSDoc comments. -4. **Verify & Refactor**: Self-evaluate the code against these sandbox assertions: - * Is `` returned at the root? - * Are events lowercase in JSX (`onclick`)? - * If a prop is reflected, is its type serializable? - * If `useProp` is used, is it properly declared under `config.props`? - * If errors or gaps are detected, refactor immediately before completing the task. +1. **Plan & Search**: Query the workspace for components with similar goals to check for potential styling variants (`variant`) or component variations, rather than coding from scratch. +2. **Analyze**: Determine the modular component tree. High complexity views must place child components under `components/` and import them. +3. **Build**: Write the custom element exporting only the component instance. Use arrow-function factories (`value: () => defaultValue`) for all property defaults. +4. **Verify & Register**: + - Register all component instances in a central index (`components/index.ts`). + - Type DOM references using the `useRef()` pattern. + - **TSX Type Check**: Explicitly run type checking (e.g. `tsc --noEmit` or verify TS compiler outputs) to ensure that all properties (especially `Array` and `Object` properties with custom type assertions) compile flawlessly without type errors (`never[]`). + diff --git a/.agents/skills/atomico/rules/component-creation.md b/.agents/skills/atomico/rules/component-creation.md index 25824bd..31c88ca 100644 --- a/.agents/skills/atomico/rules/component-creation.md +++ b/.agents/skills/atomico/rules/component-creation.md @@ -1,38 +1,52 @@ # Rule: Component Creation & Setup -Pragmatic guidelines for initializing standard Custom Elements and form-associated components using Atomico. +Pragmatic guidelines for initializing standard Custom Elements and form-associated components using Atomico, optimized for LLM procedural parsing. --- -## 1. Signature & Exports +## 1. Search-and-Variant-First (Component Reuse) -Always wrap components using the inline `c(render, config)` signature, and export the generated instance directly. +Before writing any new component file or custom element: +1. **Workspace Inspection**: Search the workspace for existing component files or tags with a similar purpose. +2. **Reusability Check**: + - If a similar component exists, you MUST propose or automatically create a **variation** (e.g., adding a styling variant like `variant="secondary"` or extending its properties) instead of duplicating code. + - If the user/context allows automatic actions, implement it as a variant. If not, explicitly ask the user if they prefer a variation. -* **Composition Rule**: Always instantiate child components in JSX using their **Constructor class name** (e.g. ``) rather than string tag names (e.g. ``) to inherit TypeScript prop typings. +--- + +## 2. Agnostic Export & Centralized Index Registration + +Component declaration files (e.g., `my-button.tsx`) MUST remain agnostic of global Custom Element registry naming collisions. + +1. **Agnostic Exports**: Only export the component instance generated by `c(render, config)`. Do NOT call `customElements.define` inside the component file! +2. **Centralized Index**: Register all custom elements globally inside a central index file (`components/index.ts`). +### Component File (`components/my-button.tsx`) ```tsx import { c } from "atomico"; -// ✅ CORRECT: Inline c() setup and direct instance export +// ✅ CORRECT: Export only the instance. NO customElements.define here! export const MyButton = c( ({ variant }) => Click me, { - props: { variant: String } + props: { + variant: { type: String, value: () => "primary", reflect: true } + } } ); - -customElements.define("my-button", MyButton); ``` -```tsx -// ❌ INCORRECT: Avoid defining props outside c() or exporting raw functions -function Button() { return Click me; } -Button.props = { variant: String }; // ❌ Too verbose / Loose typing +### Central Index File (`components/index.ts`) +```typescript +import { MyButton } from "./my-button.js"; + +// ✅ CORRECT: Centralized registration +customElements.define("my-button", MyButton); ``` --- -## 2. Root Element: `` +## 3. Root Element: `` Every component render function **MUST return a single `` root element**. @@ -41,7 +55,7 @@ Every component render function **MUST return a single `` root element**. ```tsx // ✅ CORRECT export const Card = c(() => ( - +
    Content
    )); @@ -52,37 +66,5 @@ export const Card = c(() => ( export const Card = c(() => (
    Content
    // ❌ Fatal: Missing root )); -``` - ---- - -## 3. Form-Associated Components - -To make a component participate natively in HTML `` submits, validations, and resets: - -1. **Activate internal engine**: Set `form: true` in the configuration object of `c()`. -2. **Handle Focus Delegation**: Always set `delegatesFocus: true` inside `shadowDom` configuration on the `` root. -3. **Declare standard props**: Define `name` and `value` in the `props` config. - -```tsx -import { c } from "atomico"; - -export const MyFormInput = c( - ({ name }) => { - return ( - - - - ); - }, - { - form: true, // 👈 Required for ElementInternals - props: { - name: String, - value: String - } - } -); -customElements.define("my-form-input", MyFormInput); ``` diff --git a/.agents/skills/atomico/rules/props-declaration.md b/.agents/skills/atomico/rules/props-declaration.md index 9ed06e6..17dc3f7 100644 --- a/.agents/skills/atomico/rules/props-declaration.md +++ b/.agents/skills/atomico/rules/props-declaration.md @@ -1,183 +1,190 @@ -# Props Declaration +# Rule: Props Declaration & Events -Props in Atomico allow your component to receive data and automatically sync state with HTML attributes. +Pragmatic guidelines for defining properties, reflecting attributes, typing references, and managing unidirectional and bidirectional communication in Atomico. -## Factory Defaults for Props +--- -When defining default values in the `props` configuration object, **you must use a factory callback function**, not a static raw value. This prevents memory leaks and unintended sharing of object references across component instances. +## 1. Property Syntax Selection (Shorthand vs Default Factories) -### ❌ Incorrect +According to the complexity and design objectives of each property, select the appropriate syntax configuration: -```tsx -import { c } from "atomico"; +1. **Shorthand Type Direct (`message: String`)**: Use this direct type definition by default for simple read-only properties that do not require an initial default state and do not need to reflect as attributes. This keeps the component definition clean and readable. +2. **Configuration Object with Factory Callback (`value: () => defaultValue`)**: Use a configuration object *exclusively* when the property requires an initial default state or attribute reflection (`reflect: true`). In this case, **`value` must strictly be an arrow-function factory callback** returning the value. -export const Profile = c( - ({ name, role }) => ( - - {name} - {role} - - ), - { - props: { - // ❌ BAD: Raw static values used as defaults - name: { type: String, value: "User Name" }, - tags: { type: Array, value: [] } - } - } -); +### ❌ Incorrect +```tsx +props: { + // ❌ BAD: Raw static values used as defaults in configuration objects + name: { type: String, value: "User Name" }, + counter: { type: Number, value: 0 }, + // ❌ BAD: Over-verbose configuration for a simple prop without default state or reflect + message: { type: String } +} ``` ### ✅ Correct - ```tsx -import { c } from "atomico"; - -export const Profile = c( - ({ name, role }) => ( - - {name} - {role} - - ), - { - props: { - // ✅ GOOD: Use a callback function returning the default value - name: { type: String, value: () => "User Name" }, - tags: { type: Array, value: () => [] } - } - } -); +props: { + // ✅ GOOD: Direct shorthand type for simple read-only props + message: String, + + // ✅ GOOD: Configuration object using factory callback for default states + name: { type: String, value: () => "User Name" }, + counter: { type: Number, value: () => 0, reflect: true }, + tags: { type: Array, value: () => [] } +} ``` -## Reflected Props and Variants +--- + +## 2. Reflected Props for CSS Styling -To make a property observable from the outside as an HTML attribute (which is crucial for styling variants or querying), you must set `reflect: true`. +Setting `reflect: true` is used specifically to **control component styling and visual states via CSS**. When reflected, the property maps directly to an HTML attribute on the host element, allowing the use of highly efficient CSS attribute selectors. -Types can be `String`, `Number`, `Boolean`, `Array`, `Object`. +* **Design Purpose**: Always reflect properties (such as `show`, `disabled`, `active`, or `variant`) when they represent a visual state that should trigger styling rules. +* **Reflected Types**: Allowed exclusively for simple serializable types (`String`, `Number`, `Boolean`). +* **Complex Types**: Setting `reflect: true` is strictly prohibited for `Array` or `Object` types to prevent expensive DOM serialization. ```tsx -export const Alert = c(({ variant }) => Alert: {variant}, { - props: { - // A common pattern for variants. Reflects to - variant: { - type: String, - reflect: true, - value: () => "primary" +import { c, css } from "atomico"; + +export const Alert = c( + () => Alert Box, + { + props: { + variant: { + type: String, + reflect: true, + value: () => "primary" // ✅ OK: String reflected for styling variants + }, + show: { + type: Boolean, + reflect: true, + value: () => false // ✅ OK: Boolean reflected for state toggling + } }, - // Booleans are commonly reflected for flags like "disabled" or "active" - disabled: { - type: Boolean, - reflect: true - } + // Sincronización con selectores CSS en el host: + styles: css` + :host { display: none; } + :host([show]) { display: block; } + :host([variant="danger"]) { color: red; } + ` } -}); +); ``` +--- -## TypeScript: Enforcing Specific Values +## 3. TypeScript: Inferencia y Ref Tipado (`useRef`) -Atomico allows capturing the type from the constructor directly in the `props` declaration. This is highly useful for variants or enumerations to ensure that the IDE provides autocompletion and type checking when consuming the component in JSX. +Atomico provides native support for component constructor types to type references inside hooks. -You can use the syntax `Type<"value1" | "value2">` (like `String<"yes"|"no">`) using type assertions or casting depending on your TypeScript setup, but Atomico natively understands it for inference. +* **useRef Typing**: Use `useRef()` directly. There is no need for `InstanceType` or using `any`. ```tsx -import { c } from "atomico"; - -export const Dialog = c(({ status }) => {status}, { - props: { - // ✅ GOOD: Capturing specific string types for the prop - status: { - type: String, - value: (): "success" | "warning" | "error" => "success", - reflect: true - }, +import { c, useRef } from "atomico"; +import { MyButton } from "./my-button.js"; - // Or if using Atomico's utility types (like String<...>): - message: String<"Si" | "No"> - } +export const MyForm = c(() => { + // ✅ CORRECT: Type inference directly from constructor typeof + const buttonRef = useRef(null); + + return ( + + + + ); }); +``` -## Communication and Events: `event()` and `callback()` +--- -Instead of managing manual DOM CustomEvents or raw callbacks, Atomico provides specific helpers inside the component `props` configuration to handle typings and autocomplete, representing two distinct design patterns. +## 4. Communication: Unidirectional vs Bidirectional ---- +We distinguish between two clear, non-overlapping communication patterns: -### 1. `event(config?)` — Fire and Forget (Unidirectional Notification) +### 1. `event()` — Unidirectional Notification (Fire and Forget) +Use `event()` inside `props` to notify parent elements that an action occurred. The child dispatches the event and continues execution without expecting a return value. -Use `event()` to **notify** parent components that an action occurred. It is strictly **unidirectional and upward**. The child dispatches the event and continues execution without expecting any return value. +* **Payload Mapping**: Calling `props.action(payload)` automatically wraps `payload` inside `event.detail` in standard CustomEvents. The parent reads it via `event.detail`. +* **🛑 Naming Rule**: NEVER prefix event props with "on" (e.g. use `search: event()`, NOT `onSearch`). Atomico's JSX mapping automatically prepends "on" for listeners (e.g. ``). -* **Definition**: ```tsx -import { c, event } from "atomico"; - export const ActionButton = c( (props) => { - return ( - - {/* Calling the event-prop as a function dispatches the event */} - - - ); + // Calling props.action(payload) dispatches CustomEvent with detail = payload + return ; }, { props: { - // Generates a CustomEvent of type "action" - action: event<{ id: number }>({ - bubbles: true, - composed: true // Allows crossing Shadow DOM boundaries - }) + action: event<{ id: number }>({ bubbles: true, composed: true }) } } ); ``` -* **Consumption in JSX**: Parents listen to the event using the `on` prefix. -```tsx -// JSX binds to the event with "on" + the prop name - console.log(detail.id)} /> -``` - -* **🛑 CRITICAL NAMING RULE**: NEVER prefix an `event()` prop with "on" (e.g., `onSearch`). Atomico's JSX mapping automatically prepends "on" for listeners. If you define a prop as `onSearch`, JSX will expect ``. Always name the base action (e.g., `search: event()`). - ---- +### 2. `callback()` — Bidirectional Delegated Logic (Request-Response) +Use `callback()` strictly to delegate custom logic to the parent where the child **expects a response** (e.g., async validation or custom data filtering). The flow blocks and awaits the returned value. -### 2. `callback()` — Request-Response (Bidirectional Delegated Logic) +* **🛑 Naming Rule**: NEVER prefix callback props with "on" (e.g. use `save: callback()`, NOT `onSave`). Atomico interprets any prop starting with "on" as a native event subscription. -Use `callback()` strictly to **delegate logic** to a parent component where the child **expects a response** (e.g. data filtering, async validations, or custom processors). The execution flow halts and awaits the returned value from the parent. - -* **Definition**: ```tsx -import { c, callback } from "atomico"; - export const TextEditor = c( (props) => { const handleSave = async () => { if (props.save) { - // Execute parent logic and await its value + // Await returned value from parent logic const success = await props.save(props.content); - if (success) console.log("Saved successfully!"); + if (success) console.log("Saved!"); } }; - return ; + return ; }, { props: { - content: String, + content: { type: String, value: () => "" }, save: callback<(content: string) => Promise>() } } ); ``` -* **Consumption in JSX**: +--- + +## 5. TypeScript: Strict Complex Property Typings (Arrays & Objects) + +By default, declaring a property with `type: Array` and a factory like `value: () => []` resolves the TypeScript type to `never[]`. Similarly, `type: Object` with `value: () => ({})` resolves to an empty object `{}`. + +To enforce exact typings in TSX, you **MUST** use explicit type assertions (`as Type`) inside the arrow-function factory of the property definition. + +### ❌ Incorrect (TSX Typing Errors) ```tsx - { - await saveToServer(text); - return true; - }} -/> +props: { + // ❌ BAD: Resolves to never[] inside TSX + options: { type: Array, value: () => [] }, + + // ❌ BAD: Resolves to {} instead of structured interface + config: { type: Object, value: () => ({}) } +} ``` -* **🛑 CRITICAL CALLBACK NAMING RULE**: NEVER prefix a `callback()` prop with "on" (e.g., `onSave`). Atomico's JSX parser interprets any prop starting with "on" (like `onSave`) as a native DOM event listener subscription (listening to a `Save` event). Always use clean, action-oriented names like `save: callback()`, `filter: callback()`, or `customFilter: callback()`. +### ✅ Correct (Perfect TSX Type Inference) +```tsx +interface SelectOption { + value: string; + label: string; +} + +interface AppConfig { + theme: "light" | "dark"; + debug: boolean; +} + +props: { + // ✅ GOOD: TypeScript infers options as SelectOption[] + options: { type: Array, value: () => [] as SelectOption[] }, + + // ✅ GOOD: TypeScript infers config as AppConfig + config: { type: Object, value: () => ({}) as AppConfig } +} ``` + diff --git a/developer-experience/skills-evaluator/README.md b/developer-experience/skills-evaluator/README.md new file mode 100644 index 0000000..a57d166 --- /dev/null +++ b/developer-experience/skills-evaluator/README.md @@ -0,0 +1,23 @@ +# Atomico Evaluation Sandbox + +> [!IMPORTANT] +> **Cognitive Directive**: Before initiating any action, tool call, or web search, you are strictly required to read, index, and apply the specialized design and architecture instructions declared in your local `.agents/skills/` directory. Do not perform external scaffolding searches or initializations, as the environment is already pre-configured. + +Welcome to the skill evaluation workspace. The goal of this environment is to assess the engineering quality and architecture of custom interfaces built using **Atomico.js**. + +## The Task + +Build a fully functional, highly polished **Task Management Application (Todo App)** inside the `./sandbox/` directory. + +### Requirements: +* You **MUST** build this application using **Atomico** (`import { c, css, ... } from "atomico"`). +* The application must allow users to manage their daily tasks (e.g., adding, updating status, and filtering tasks) through an elegant, interactive UI. +* Ensure that the overall architecture is clean, maintainable, and aligned with standard Custom Elements best practices. + +--- + +## How to Proceed + +1. Analyze this workspace and the available local agents/skills. +2. Implement the entire task manager inside the `./sandbox/` directory. +3. Ensure everything works flawlessly without human intervention. Report your final implementation once you are done. diff --git a/developer-experience/skills-evaluator/index.html b/developer-experience/skills-evaluator/index.html new file mode 100644 index 0000000..a535a87 --- /dev/null +++ b/developer-experience/skills-evaluator/index.html @@ -0,0 +1,29 @@ + + + + + + Personal Workspace - Productivity Dashboard + + + + + + + + + + + + + + + diff --git a/developer-experience/skills-evaluator/package.json b/developer-experience/skills-evaluator/package.json index 52a5ebf..02fd9be 100644 --- a/developer-experience/skills-evaluator/package.json +++ b/developer-experience/skills-evaluator/package.json @@ -3,7 +3,8 @@ "version": "1.0.0", "description": "", "scripts": { - "test": "agy --sandbox --dangerously-skip-permissions --ephemeral < step-1.md" + "start": "vite", + "test": "agy --sandbox --dangerously-skip-permissions < README.md" }, "keywords": [], "author": "", diff --git a/developer-experience/skills-evaluator/sandbox/basic-component.tsx b/developer-experience/skills-evaluator/sandbox/basic-component.tsx deleted file mode 100644 index 716e1c2..0000000 --- a/developer-experience/skills-evaluator/sandbox/basic-component.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { c, css, event } from "atomico"; - -export const UiButton = c( - (props) => { - return ( - - - - ); - }, - { - props: { - variant: { - type: String, - reflect: true, - value: () => "primary" - }, - action: event({ bubbles: true, composed: true }) - }, - styles: css` - :host { - display: inline-block; - --btn-bg: linear-gradient(135deg, #6366f1, #4f46e5); - --btn-color: #ffffff; - --btn-border: none; - --btn-shadow: 0 4px 12px rgba(79, 70, 229, 0.25); - --btn-hover-bg: linear-gradient(135deg, #4f46e5, #4338ca); - --btn-hover-shadow: 0 6px 16px rgba(79, 70, 229, 0.35); - --btn-active-scale: 0.96; - font-family: 'Inter', system-ui, sans-serif; - } - - :host([variant="secondary"]) { - --btn-bg: linear-gradient(135deg, #4b5563, #374151); - --btn-color: #f3f4f6; - --btn-shadow: 0 4px 12px rgba(55, 65, 81, 0.2); - --btn-hover-bg: linear-gradient(135deg, #374151, #1f2937); - --btn-hover-shadow: 0 6px 16px rgba(55, 65, 81, 0.3); - } - - :host([variant="danger"]) { - --btn-bg: linear-gradient(135deg, #ef4444, #dc2626); - --btn-color: #ffffff; - --btn-shadow: 0 4px 12px rgba(220, 38, 38, 0.25); - --btn-hover-bg: linear-gradient(135deg, #dc2626, #b91c1c); - --btn-hover-shadow: 0 6px 16px rgba(220, 38, 38, 0.35); - } - - :host([variant="outline"]) { - --btn-bg: transparent; - --btn-color: #6366f1; - --btn-border: 2px solid #6366f1; - --btn-shadow: none; - --btn-hover-bg: rgba(99, 102, 241, 0.08); - --btn-hover-shadow: 0 4px 12px rgba(99, 102, 241, 0.1); - } - - .btn { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0.75rem 1.5rem; - font-size: 0.95rem; - font-weight: 600; - line-height: 1.25; - color: var(--btn-color); - background: var(--btn-bg); - border: var(--btn-border); - border-radius: 10px; - cursor: pointer; - box-shadow: var(--btn-shadow); - transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); - user-select: none; - outline: none; - } - - .btn:hover { - background: var(--btn-hover-bg); - box-shadow: var(--btn-hover-shadow); - transform: translateY(-2px); - } - - .btn:active { - transform: translateY(0) scale(var(--btn-active-scale)); - box-shadow: var(--btn-shadow); - } - - .btn:focus-visible { - outline: 2px solid #818cf8; - outline-offset: 2px; - } - ` - } -); - -customElements.define("ui-button", UiButton); diff --git a/developer-experience/skills-evaluator/sandbox/components/dashboard-stats.tsx b/developer-experience/skills-evaluator/sandbox/components/dashboard-stats.tsx new file mode 100644 index 0000000..1b2c284 --- /dev/null +++ b/developer-experience/skills-evaluator/sandbox/components/dashboard-stats.tsx @@ -0,0 +1,221 @@ +import { c, css, useProp } from "atomico"; + +export const DashboardStats = c( + () => { + const [total] = useProp("total"); + const [completed] = useProp("completed"); + const [inProgress] = useProp("inProgress"); + const [pending] = useProp("pending"); + + const completionRate = total > 0 ? Math.round((completed / total) * 100) : 0; + + // SVG circle stroke calculations + const radius = 36; + const circumference = 2 * Math.PI * radius; + const strokeDashoffset = circumference - (completionRate / 100) * circumference; + + return ( + +
    +
    +
    +

    Productivity

    +

    Your daily goal progression

    +
    + {completed} of {total} tasks completed +
    +
    +
    + + + + +
    + {completionRate}% +
    +
    +
    + +
    +
    + +
    + Pending + {pending} +
    +
    +
    + +
    + In Progress + {inProgress} +
    +
    +
    + +
    + Completed + {completed} +
    +
    +
    +
    +
    + ); + }, + { + props: { + total: { type: Number, value: () => 0 }, + completed: { type: Number, value: () => 0 }, + inProgress: { type: Number, value: () => 0 }, + pending: { type: Number, value: () => 0 } + }, + styles: css` + :host { + display: block; + width: 100%; + } + .stats-grid { + display: grid; + grid-template-columns: 1.5fr 1fr; + gap: 1.25rem; + width: 100%; + } + @media (max-width: 768px) { + .stats-grid { + grid-template-columns: 1fr; + } + } + .card { + background: #ffffff; + border-radius: 16px; + padding: 1.5rem; + box-shadow: 0 4px 20px -2px rgba(156, 163, 175, 0.15), 0 2px 8px -1px rgba(156, 163, 175, 0.1); + border: 1px solid rgba(229, 231, 235, 0.7); + display: flex; + position: relative; + overflow: hidden; + } + .progress-card { + justify-content: space-between; + align-items: center; + background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%); + } + .progress-info { + display: flex; + flex-direction: column; + } + h3 { + margin: 0; + font-size: 1.25rem; + font-weight: 700; + color: #0f172a; + } + .subtitle { + margin: 0.25rem 0 1rem 0; + font-size: 0.8125rem; + color: #64748b; + } + .stats-summary { + font-size: 0.875rem; + color: #475569; + } + .stats-summary .highlight { + font-weight: 700; + color: #2563eb; + } + .stats-summary .total { + font-weight: 600; + color: #0f172a; + } + .radial-container { + position: relative; + width: 90px; + height: 90px; + display: flex; + align-items: center; + justify-content: center; + } + .radial-svg { + width: 100%; + height: 100%; + transform: rotate(-90deg); + } + .radial-bg { + fill: none; + stroke: #f1f5f9; + stroke-width: 8; + } + .radial-bar { + fill: none; + stroke: url(#progressGrad); + stroke: #2563eb; /* Fallback */ + stroke-width: 8; + stroke-linecap: round; + transition: stroke-dashoffset 0.6s cubic-bezier(0.4, 0, 0.2, 1); + } + .radial-text { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + } + .percentage { + font-size: 1.125rem; + font-weight: 700; + color: #0f172a; + } + .detail-cards { + display: flex; + flex-direction: column; + gap: 0.75rem; + } + .mini-card { + padding: 0.875rem 1.25rem; + display: flex; + align-items: center; + gap: 0.75rem; + } + .dot { + width: 8px; + height: 8px; + border-radius: 50%; + flex-shrink: 0; + } + .mini-card.pending .dot { + background-color: #f59e0b; + box-shadow: 0 0 8px #f59e0b; + } + .mini-card.progress .dot { + background-color: #3b82f6; + box-shadow: 0 0 8px #3b82f6; + } + .mini-card.completed .dot { + background-color: #10b981; + box-shadow: 0 0 8px #10b981; + } + .card-content { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + } + .label { + font-size: 0.8125rem; + font-weight: 500; + color: #64748b; + } + .value { + font-size: 1.125rem; + font-weight: 700; + color: #0f172a; + } + ` + } +); diff --git a/developer-experience/skills-evaluator/sandbox/components/index.ts b/developer-experience/skills-evaluator/sandbox/components/index.ts new file mode 100644 index 0000000..6b6b3f0 --- /dev/null +++ b/developer-experience/skills-evaluator/sandbox/components/index.ts @@ -0,0 +1,20 @@ +import { UiBadge } from "./ui-badge.js"; +import { UiButton } from "./ui-button.js"; +import { UiCheckbox } from "./ui-checkbox.js"; +import { UiInput } from "./ui-input.js"; +import { UiSelect } from "./ui-select.js"; +import { DashboardStats } from "./dashboard-stats.js"; +import { TaskItem } from "./task-item.js"; +import { TaskForm } from "./task-form.js"; +import { TodoApp } from "../todo-app-component.js"; + +// Register all custom elements agnostically to avoid conflicts +customElements.define("ui-badge", UiBadge); +customElements.define("ui-button", UiButton); +customElements.define("ui-checkbox", UiCheckbox); +customElements.define("ui-input", UiInput); +customElements.define("ui-select", UiSelect); +customElements.define("dashboard-stats", DashboardStats); +customElements.define("task-item", TaskItem); +customElements.define("task-form", TaskForm); +customElements.define("todo-app", TodoApp); diff --git a/developer-experience/skills-evaluator/sandbox/components/task-form.tsx b/developer-experience/skills-evaluator/sandbox/components/task-form.tsx new file mode 100644 index 0000000..4e07bec --- /dev/null +++ b/developer-experience/skills-evaluator/sandbox/components/task-form.tsx @@ -0,0 +1,299 @@ +import { c, css, useState, useEffect, useEvent, useProp } from "atomico"; +import { UiInput } from "./ui-input.js"; +import { UiSelect } from "./ui-select.js"; +import { UiButton } from "./ui-button.js"; + +interface Task { + id?: string; + title: string; + description: string; + category: string; + priority: "High" | "Medium" | "Low"; + dueDate: string; + status: "Pending" | "In Progress" | "Completed"; +} + +export const TaskForm = c( + ({ task, active }) => { + const dispatchSubmit = useEvent("submit-task", { bubbles: true }); + const dispatchClose = useEvent("close", { bubbles: true }); + + const t = task as any; + + const [title, setTitle] = useState(""); + const [description, setDescription] = useState(""); + const [category, setCategory] = useState("Personal"); + const [priority, setPriority] = useState<"High" | "Medium" | "Low">("Low"); + const [dueDate, setDueDate] = useState(""); + const [status, setStatus] = useState<"Pending" | "In Progress" | "Completed">("Pending"); + + // Sync local state when task prop changes + useEffect(() => { + if (t && t.id) { + setTitle(t.title || ""); + setDescription(t.description || ""); + setCategory(t.category || "Personal"); + setPriority(t.priority || "Low"); + setDueDate(t.dueDate || ""); + setStatus(t.status || "Pending"); + } else { + setTitle(""); + setDescription(""); + setCategory("Personal"); + setPriority("Low"); + setDueDate(""); + setStatus("Pending"); + } + }, [t]); + + const handleSubmit = (e: Event) => { + e.preventDefault(); + if (!title.trim()) return; + + dispatchSubmit({ + id: t?.id, + title, + description, + category, + priority, + dueDate, + status + }); + }; + + const categoryOptions = [ + { value: "Work", label: "Work" }, + { value: "Personal", label: "Personal" }, + { value: "Shopping", label: "Shopping" }, + { value: "Urgent", label: "Urgent" } + ]; + + const priorityOptions = [ + { value: "High", label: "High" }, + { value: "Medium", label: "Medium" }, + { value: "Low", label: "Low" } + ]; + + const statusOptions = [ + { value: "Pending", label: "Pending" }, + { value: "In Progress", label: "In Progress" }, + { value: "Completed", label: "Completed" } + ]; + + const isEditing = t && t.id; + + + return ( + +
    dispatchClose()}>
    +