Skip to content

Commit 685ba75

Browse files
committed
Updates to the component developmnt guide.
1 parent df77bd7 commit 685ba75

File tree

2 files changed

+296
-271
lines changed

2 files changed

+296
-271
lines changed

dev/component_dev.md

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
# Component Development Guide (AI Operations Manual)
2+
3+
This guide provides a technical walkthrough for an AI agent on how to create and update the structural components of the website. Components are the foundational building blocks of the site, written as Astro (`.astro`) files.
4+
5+
The core philosophy is a strict **separation of concerns**:
6+
7+
* **Structure (Components)**: Defined in `.astro` files. This is your focus when following this guide. Components are responsible for the HTML structure and accepting data via props. They should be completely agnostic of the content they display and the specific styles they wear.
8+
* **Content (Data)**: Defined in `.yaml` files in the `/content` directory. This is managed according to the [Content Management Guide](content_management.rst).
9+
* **Style (Theme)**: Defined in the `/src/themes/[theme-name]/` directory. This is managed according to the [AI Theming Engine Guide](theme_design.rst).
10+
11+
Your role in component development is to act as an architect, creating robust and flexible structures that can be filled with any content and styled by any theme.
12+
13+
## Component Categories & Templates
14+
15+
When tasked with creating a "component", first determine its type, then strictly adhere to the corresponding template. Don't improvise on the architecture; use these proven patterns.
16+
17+
### 1. UI Components (Atoms)
18+
19+
Small custom elements (e.g., buttons, badges, icons). Use this for reusable UI bits.
20+
21+
**Template Path**: `src/core/dev/templates/ui/GenericElement.astro`
22+
23+
**Style Template**: `src/core/dev/templates/ui/GenericElement.style.yaml`
24+
**Style Spec Template**: `src/core/dev/templates/ui/GenericElement.style.spec.yaml`
25+
26+
```astro
27+
---
28+
/**
29+
* GenericElement.astro
30+
*/
31+
import type { HTMLAttributes } from 'astro/types';
32+
import { getClasses } from '~/utils/theme';
33+
import { twMerge } from 'tailwind-merge';
34+
35+
interface Props extends HTMLAttributes<'div'> {
36+
variant?: 'default' | 'alternative';
37+
}
38+
39+
const {
40+
variant = 'default',
41+
class: className = '',
42+
...rest
43+
} = Astro.props;
44+
45+
// Retrieve CSS classes
46+
const classes = getClasses('Component+GenericElement');
47+
48+
const variants = {
49+
default: classes.default,
50+
alternative: classes.alternative,
51+
};
52+
---
53+
54+
<div class={twMerge(variants[variant], className)} {...rest}>
55+
<slot />
56+
</div>
57+
```
58+
59+
```yaml
60+
# GenericElement.style.yaml
61+
Component+GenericElement:
62+
default: 'rounded p-4 bg-surface text-default'
63+
alternative: 'rounded p-4 bg-primary text-white'
64+
```
65+
66+
```yaml
67+
# GenericElement.style.spec.yaml
68+
Component+GenericElement:
69+
type: object
70+
properties:
71+
default: { type: string }
72+
alternative: { type: string }
73+
```
74+
75+
### 2. Standard Widgets (Molecules)
76+
77+
Self-contained content blocks (e.g., Forms, Charts, Simple Cards).
78+
79+
**Template Path**: `src/core/dev/templates/widgets/GenericWidget.astro`
80+
**Spec Path**: `src/core/dev/templates/widgets/GenericWidget.spec.yaml`
81+
**Style Template**: `src/core/dev/templates/widgets/GenericWidget.style.yaml`
82+
**Style Spec Template**: `src/core/dev/templates/widgets/GenericWidget.style.spec.yaml`
83+
84+
**Key Pattern**: Inherits `WidgetProps`, uses `WidgetWrapper`.
85+
86+
```astro
87+
---
88+
import type { Widget as WidgetProps } from '~/types';
89+
import WidgetWrapper from '~/components/ui/WidgetWrapper.astro';
90+
import { getClasses } from '~/utils/theme';
91+
92+
export interface Props extends WidgetProps {
93+
customProp?: string;
94+
}
95+
96+
const { id, customProp, classes: rawClasses = {}, bg = '' } = Astro.props;
97+
const classes = getClasses('Component+GenericWidget', rawClasses);
98+
---
99+
100+
<WidgetWrapper type="generic-widget" id={id} classes={classes.wrapper} bg={bg}>
101+
<div class={classes.container}>
102+
{customProp && <p class={classes.text}>{customProp}</p>}
103+
<slot />
104+
</div>
105+
</WidgetWrapper>
106+
```
107+
108+
```yaml
109+
# GenericWidget.style.yaml
110+
Component+GenericWidget:
111+
wrapper:
112+
container: ''
113+
container: 'flex flex-col gap-4'
114+
text: 'text-lg font-medium'
115+
```
116+
117+
### 3. Titled Widgets (Complex Molecules)
118+
119+
Widgets that need a header (Title, Subtitle, Tagline) like Hero, Features, FAQs. This is the **most common** type of widget you will build.
120+
121+
**Template Path**: `src/core/dev/templates/widgets/GenericTitledWidget.astro`
122+
**Spec Path**: `src/core/dev/templates/widgets/GenericTitledWidget.spec.yaml`
123+
**Style Template**: `src/core/dev/templates/widgets/GenericTitledWidget.style.yaml`
124+
**Style Spec Template**: `src/core/dev/templates/widgets/GenericTitledWidget.style.spec.yaml`
125+
126+
**Key Pattern**: Inherits `TitledWidget`, uses `TitledWidgetWrapper`.
127+
128+
```astro
129+
---
130+
import type { TitledWidget } from '~/types';
131+
import TitledWidgetWrapper from '~/components/ui/TitledWidgetWrapper.astro';
132+
import { getClasses } from '~/utils/theme';
133+
134+
export interface Props extends TitledWidget {
135+
content?: string;
136+
}
137+
138+
const {
139+
id,
140+
title = await Astro.slots.render('title'),
141+
subtitle = await Astro.slots.render('subtitle'),
142+
tagline = await Astro.slots.render('tagline'),
143+
content,
144+
classes: rawClasses = {},
145+
bg = '',
146+
} = Astro.props;
147+
148+
const classes = getClasses('Component+GenericTitledWidget', rawClasses);
149+
---
150+
151+
<TitledWidgetWrapper
152+
type="generic-titled-widget"
153+
id={id}
154+
classes={classes}
155+
bg={bg}
156+
title={title}
157+
subtitle={subtitle}
158+
tagline={tagline}
159+
>
160+
<div class={classes.content_container}>
161+
{content && <p class={classes.content}>{content}</p>}
162+
<slot />
163+
</div>
164+
</TitledWidgetWrapper>
165+
```
166+
167+
```yaml
168+
# GenericTitledWidget.style.yaml
169+
Component+GenericTitledWidget:
170+
wrapper:
171+
container: ''
172+
headline:
173+
title: ''
174+
content_container: 'mt-8 flex flex-col gap-6'
175+
content: 'text-muted'
176+
```
177+
178+
### 4. Section Components (Organisms)
179+
180+
Layout containers that hold Widgets.
181+
182+
**Template Path**: `src/core/dev/templates/sections/GenericSection.astro`
183+
**Spec Path**: `src/core/dev/templates/sections/GenericSection.spec.yaml`
184+
**Style Template**: `src/core/dev/templates/sections/GenericSection.style.yaml`
185+
**Style Spec Template**: `src/core/dev/templates/sections/GenericSection.style.spec.yaml`
186+
187+
**Key Pattern**: Uses `SectionWrapper`, iterates over `components.main` using `generateSection`.
188+
189+
```astro
190+
---
191+
import type { Section as Props } from '~/types';
192+
import SectionWrapper from '~/components/ui/SectionWrapper.astro';
193+
import { generateSection } from '~/utils/generator';
194+
import { getClasses } from '~/utils/theme';
195+
196+
// Destructure section properties
197+
const { id, components, classes: rawClasses = {}, bg = '' } = Astro.props;
198+
const classes = getClasses('Section+GenericSection', rawClasses);
199+
---
200+
201+
<SectionWrapper type="generic-section" id={id} classes={classes.wrapper} bg={bg}>
202+
<div class={classes.container} data-name="section-generic-container">
203+
<div class={classes.content} data-name="section-generic-content">
204+
{
205+
generateSection(components?.main).map(({ component: Component, props }) => (
206+
<Component {...props} />
207+
))
208+
}
209+
</div>
210+
</div>
211+
</SectionWrapper>
212+
```
213+
214+
```yaml
215+
# GenericSection.style.yaml
216+
Section+GenericSection:
217+
wrapper:
218+
container: 'py-10 md:py-16'
219+
main: ''
220+
```
221+
222+
## Development Workflow
223+
224+
Follow this exact process to create a new component.
225+
226+
### Step 1: Select Type & Template
227+
Choose strictly from the 4 types above. Do not invent new structures.
228+
* Need a button or label? -> **UI Component**
229+
* Need a content block with a title? -> **Titled Widget** (Most common)
230+
* Need a raw content block? -> **Standard Widget**
231+
* Need a new page layout? -> **Section Component**
232+
233+
### Step 2: Create the Files (The "4-File Rule")
234+
Every functional component requires **exactly four files** to be complete. This ensures the component is structurally sound, spec-compliant, and fully themeable.
235+
236+
1. **Component File** (`.astro`): The HTML structure and logic.
237+
2. **Component Spec** (`.spec.yaml`): The content schema (props validation).
238+
3. **Style Definition** (`.style.yaml`): The default default styles (Tailwind classes).
239+
4. **Style Spec** (`.style.spec.yaml`): The style schema (validating the theme).
240+
241+
**Copy all four templates** for your chosen type into the component's directory.
242+
* **Location**: `src/[module]/src/components/[type]/`
243+
* **Naming**:
244+
- `MyComponent.astro`
245+
- `MyComponent.spec.yaml`
246+
- `MyComponent.style.yaml` (Note: In the final module structure, these are consolidated, but start by creating them individually or adding to the module's main `style.yaml` and `style.spec.yaml`).
247+
248+
### Step 3: Define "The API" (Props)
249+
In the `.astro` file, define the `Props` interface.
250+
* **Strings**: For text content (`title`, `description`).
251+
* **Booleans**: For toggles (`isReversed`).
252+
* **Objects**: For complex data (`image: { src, alt }`).
253+
* **Arrays**: For lists (`items: []`).
254+
255+
**Crucial**: Update the `.spec.yaml` to Match!
256+
Every prop in the interface must have a corresponding entry in the spec file. This allows the AI System to validate content against your component.
257+
258+
### Step 4: Implement Structure & Styling
259+
* **Semantic HTML**: Use proper tags (`<article>`, `<figure>`, `<header>`).
260+
* **Theme Integration**:
261+
1. Call `getClasses('Component+[Name]')`.
262+
2. For every styled element, look for a class in the `classes` object (`class={classes.container}`).
263+
3. **NEVER hardcode Tailwind classes** (e.g., `class="p-4 bg-blue-500"`). Always use the theme system. This is non-negotiable.
264+
265+
### Step 5: Register Component
266+
Run `npm run generate-spec` to register your new component in the global schema.
267+
268+
### Step 6: Default Theme (Consolidated)
269+
Once you have defined your `.style.yaml` and `.style.spec.yaml`, you must merge them into the module's main theme files (or the site's default theme if working in core).
270+
271+
1. **Merge Styles**: Append the content of your `.style.yaml` to `src/[module]/theme/style.yaml`.
272+
2. **Merge Specs**: Append the content of your `.style.spec.yaml` to `src/[module]/theme/style.spec.yaml`.
273+
274+
## Engineering Standards & Best Practices
275+
276+
### Defensive Rendering Patterns
277+
AI-generated components must be robust. Never assume data exists.
278+
279+
1. **Conditional Rendering**: Do not render empty containers.
280+
* **Bad**: `<div class="subtitle">{subtitle}</div>`
281+
* **Good**: `{subtitle && <p class={classes.subtitle}>{subtitle}</p>}`
282+
283+
2. **Default Values**: Always provide fallback values for visual props.
284+
* **Example**: `const { icon = 'tabler:star', ... } = Astro.props;`
285+
286+
### Accessibility (A11y)
287+
1. **Semantic HTML**: Use `<header>`, `<article>`, `<figure>`.
288+
2. **Interactive Elements**: must have `aria-expanded` if they toggle content.
289+
3. **Images**: `alt` text is mandatory.
290+
291+
### Client-Side Interactivity
292+
1. **Scoped Selection**: use `getElementById(id)` using the component's unique `id` prop.
293+
* **Bad**: `document.querySelector('.my-button')`
294+
* **Good**: `const container = document.getElementById(id); container.querySelector('.my-button');`
295+
296+
2. **Nano Stores**: For shared state management, prefer Nano Stores over passing callbacks.

0 commit comments

Comments
 (0)