Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
272 changes: 272 additions & 0 deletions ai/skills/aidd-ecs/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
---
name: aidd-ecs
description: Enforces @adobe/data/ecs best practices. Use this whenever @adobe/data/ecs is imported, when creating or modifying Database.Plugin definitions, or when working with ECS components, resources, transactions, actions, systems, or services.
---

# Database.Plugin authoring

Plugins are created with `Database.Plugin.create()` from `@adobe/data/ecs`.

## Property order (enforced at runtime)

Properties **must** appear in this exact order. All are optional.

```sudolang
PluginPropertyOrder [
"extends — Plugin, base plugin to extend"
"services — (db) => ServiceInstance, singleton service factories"
"components — schema object, ECS component schemas"
"resources — { default: value as Type }, global resource schemas"
"archetypes — ['comp1', 'comp2'], standard ECS archetypes; storage tables for efficient insertions"
"computed — (db) => Observe<T>, computed observables"
"transactions — (store, payload) => void, synchronous deterministic atomic mutations"
"actions — (db, payload) => T, general functions"
"systems — { create: (db) => fn | void }, per-frame (60fps) or init-only"
]

Constraints {
Properties must appear in this exact order; wrong order throws at runtime
}
```

---

## Composition

**Single extension** — one plugin extends another:
```ts
export const authPlugin = Database.Plugin.create({
extends: environmentPlugin,
services: {
auth: db => AuthService.createLazy({ services: db.services }),
},
});
```

**Combine** — `extends` accepts only one plugin. To extend from multiple use Database.Plugin.combine:
```ts
export const generationPlugin = Database.Plugin.create({
extends: Database.Plugin.combine(aPlugin, bPlugin),
computed: {
max: db => Observe.withFilter(
Observe.fromProperties({
a: db.observe.resources.a,
b: db.observe.resources.b
}),
({ a, b }) => Math.max(a, b)
)
},
});
```

**Final composition** — combine all plugins into the app plugin:
```ts
export const appPlugin = Database.Plugin.combine(
corePlugin, themePlugin, dataPlugin,
authPlugin, uiPlugin, featurePlugin
);

export type AppPlugin = typeof appPlugin;
export type AppDatabase = Database.Plugin.ToDatabase<AppPlugin>;
```

---

## Property details

### services

Factory functions creating singleton services. Extended plugin services initialize first, so `db.services` has access to them.

```ts
services: {
environment: _db => EnvironmentService.create(),
}
```

### components

Schema objects defining ECS component data. Use schema imports from type namespaces or inline schemas. See [data-modeling.md](data-modeling.md) for a simple example.

```ts
components: {
layout: Layout.schema,
layoutElement: { default: null as unknown as HTMLElement, transient: true },
layoutLayer: F32.schema,
},
```

Non-persistable values (e.g. HTML elements, DOM refs) must use `transient: true` — excluded from serialization.

### resources

Global state not tied to entities. Use `as Type` to provide the compile-time type — without it the value is treated as a const literal. See [data-modeling.md](data-modeling.md) for patterns.

```ts
resources: {
themeColor: { default: 'dark' as ThemeColor },
themeScale: { default: 'medium' as ThemeScale },
},
```

Use `null as unknown as Type` for resources initialized later in a system initializer:

```ts
resources: {
connection: { default: null as unknown as WebSocket },
},
```

### archetypes

Standard ECS archetypes. Used for querying and inserting related components. See [data-modeling.md](data-modeling.md) for a simple example.

```ts
archetypes: {
Layout: ['layout', 'layoutElement', 'layoutLayer'],
},
```

### computed

Factory returning `Observe<T>` or `(...args) => Observe<T>`. Receives full db.

```ts
computed: {
max: db => Observe.withFilter(
Observe.fromProperties({
a: db.observe.resources.a,
b: db.observe.resources.b,
}),
({ a, b }) => Math.max(a, b)
),
},
```

### transactions

Synchronous, deterministic atomic mutations. Receive `store` and a payload. Store allows direct, immediate mutation of all entities, components, and resources.

```ts
transactions: {
updateLayout: (store, { entity, layout }: { entity: Entity; layout: Layout }) => {
store.update(entity, { layout });
},
setThemeColor: (store, color: ThemeColor) => {
store.resources.themeColor = color;
},
},
```

```sudolang
StoreAPI {
"store.update(entity, data)" = "update entity components"
"store.resources.x = value" = "mutate resources"
"store.get(entity, 'component')" = "read component value"
"store.read(entity)" = "read all entity component values"
"store.read(entity, archetype)" = "read entity component values in archetype"
"store.select(archetype.components, { where })" = "query entities"
}
```

### actions

General functions with access to the full db. Can return anything or nothing.
UI components that call actions MUST never consume returned values — call for side effects only. Consuming return values violates unidirectional flow (data down via Observe, actions up as void).
Call at most one transaction per action; multiple transactions corrupt the undo/redo stack.

```ts
actions: {
generateNewName: async (db) => {
const generatedName = await db.services.nameGenerator.generateName();
db.transactions.setName(generatedName);
},
getAuth: db => db.services.auth,
},
```

### systems

`create` receives db and may optionally return a per-frame function (60fps) or just initialize values. Always called synchronously when `database.extend(plugin)` runs.

```ts
systems: {
ui_state_plugin_initialize: {
create: db => {
db.transactions.registerViews(views);
},
},
layout_plugin__system: {
create: db => {
const observer = new ResizeObserver(/* ... */);
Database.observeSelectDeep(db, db.archetypes.Layout.components)(entries => {
// react to entity changes
});
},
},
},
```

**System scheduling** (optional):
```ts
systems: {
physics: {
create: db => () => { /* per-tick work */ },
schedule: {
before: ['render'],
after: ['input'],
during: ['simulation'],
},
},
},
```

```sudolang
Schedule {
before: "hard ordering constraints"
after: "hard ordering constraints"
during: "soft preference for same execution tier"
}
```

---

## Naming conventions

```sudolang
PluginNaming {
file: "*-plugin.ts (kebab-case) — e.g. layout-plugin.ts"
export: "*Plugin (camelCase) — e.g. layoutPlugin"
system: "plugin_name__system (snake_case, double underscore) — e.g. layout_plugin__system"
initSystem: "plugin_name_initialize — e.g. ui_state_plugin_initialize"
}
```

---

## Type utilities

```ts
export type MyDatabase = Database.Plugin.ToDatabase<typeof myPlugin>;
export type MyStore = Database.Plugin.ToStore<typeof myPlugin>;
```

---

## Execute

```sudolang
fn whenCreatingOrModifyingPlugin() {
Constraints {
Verify property order matches (extends, services, components, resources, archetypes, computed, transactions, actions, systems)
Use extends for single-parent; Database.Plugin.combine() for multiple peers
Ensure services only access db.services from extended plugins (not forward references)
Export type *Database = Database.Plugin.ToDatabase<typeof *Plugin> when consumers need typed db access
Follow naming conventions for files, exports, and systems
}
}
```

## Additional resources

- [data-modeling.md](data-modeling.md) — Components, resources, and archetypes (particle simulation example)
35 changes: 35 additions & 0 deletions ai/skills/aidd-ecs/data-modeling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Data modeling: components, resources, archetypes

Reference for modeling components, resources, and archetypes. Data modeling only — no transactions, systems, or higher-level plugin structure. See [SKILL.md](SKILL.md) for full plugin authoring.

---

## Particle simulation example

```ts
import { Database } from '@adobe/data/ecs';
import { Vec3, Vec4, F32 } from '@adobe/data/math';

const particleDataPlugin = Database.Plugin.create({
components: {
position: Vec3.schema,
velocity: Vec3.schema,
color: Vec4.schema,
mass: F32.schema,
},
resources: {
gravity: { default: 9.8 as number },
},
archetypes: {
Particle: ['position', 'velocity', 'color', 'mass'],
},
});
```

---

## Guidelines

- **Components**: Per-entity data. Use schema imports (Vec3, Vec4, F32 from `@adobe/data/math`) or type namespaces for custom shapes.
- **Resources**: Global state. Use **only** `{ default: value as Type }`.
- **Archetypes**: One per entity kind. List all components that kind requires.
63 changes: 63 additions & 0 deletions ai/skills/aidd-layout/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
name: aidd-layout
description: Enforces UI component layout and composition patterns. Use when designing layouts, creating UI components, spacing, gaps, or when the user asks about component hierarchy, terminal vs layout components, or re-render efficiency.
---

# Layout and component types

UI components are one of two types with no overlap. Every component is either terminal or layout.

---

## Terminal components

```sudolang
TerminalComponent {
renders: "own UI (buttons, inputs, cards, text, etc.)"
contains: "CSS for appearance"
never: "contain any external margin"
role: "leaves of the component tree; own visual styling and content"
}
```

---

## Layout components

```sudolang
LayoutComponent {
doesNotRender: "any UI themselves"
composedOf: "other layout or terminal components only"
responsibleFor: "interior gaps between children"
never: "contain any external margin"
css: "Should not need CSS 90% of the time or more — use standard layout tokens"
reRender: "Generally no business logic; should never re-render — keeps re-renders at terminal levels"
exception: "Some layout components explicitly manage state (animating, tabs, accordions) — those have logic and may re-render"
}
```

Should not need CSS 90% of the time or more — use the standard layout tokens. See [design-tokens](references/design-tokens.md).

---

## Why this split matters

Layout components that rarely re-render are efficient. State changes and user interactions trigger updates at terminal levels; layout structure above stays stable. Avoid putting reactive logic in layout components unless the layout itself is dynamic (tabs, accordions, animations).

---

## Execute

```sudolang
fn whenCreatingOrModifyingUIComponent() {
Constraints {
Classify as terminal or layout; no overlap
Terminal: owns UI and CSS; never external margin
Layout: no own UI; only layout/terminal children; owns interior gaps; never external margin
Prefer standard layout tokens for layout components; avoid custom CSS when possible
Keep layout components free of business logic unless they explicitly manage layout state (tabs, accordions, animations)
}
}
```

See [design-tokens](references/design-tokens.md) for token reference.
Loading