@adobe/data-ai 0.9.84 → 0.9.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/.claude/rules/cohesion.md +2 -2
  2. package/.claude/rules/data-modelling.md +2 -0
  3. package/.claude/rules/ecs.md +275 -0
  4. package/.claude/rules/element.md +3 -1
  5. package/.claude/rules/features/data/state.md +2 -0
  6. package/.claude/rules/features/ecs/conformance.md +5 -3
  7. package/.claude/rules/features/ecs/systems.md +61 -5
  8. package/.claude/rules/features/ui/binding-element.md +226 -0
  9. package/.claude/rules/features/ui/lazy-wrapper.md +27 -0
  10. package/.claude/rules/features/ui/presentation-test.md +38 -0
  11. package/.claude/rules/{presentation.md → features/ui/presentation.md} +2 -32
  12. package/.claude/rules/function-references.md +2 -2
  13. package/.claude/rules/hooks.md +57 -0
  14. package/.claude/rules/layout.md +132 -0
  15. package/.claude/rules/lazy-element.md +1 -1
  16. package/.claude/rules/lit.md +115 -0
  17. package/.claude/rules/namespace.md +93 -135
  18. package/.claude/rules/observe.md +183 -0
  19. package/.claude/rules/react.md +102 -0
  20. package/.claude/rules/service.md +164 -0
  21. package/.claude/rules/type-casts.md +2 -2
  22. package/.claude-plugin/plugin.json +1 -1
  23. package/README.md +24 -4
  24. package/package.json +3 -3
  25. package/skills/build-actions/SKILL.md +3 -1
  26. package/skills/build-application/SKILL.md +5 -3
  27. package/skills/build-computed/SKILL.md +2 -0
  28. package/skills/build-data/SKILL.md +4 -0
  29. package/skills/build-feature/SKILL.md +12 -15
  30. package/skills/build-game/SKILL.md +4 -2
  31. package/skills/build-indexes/SKILL.md +2 -0
  32. package/skills/build-services/SKILL.md +2 -0
  33. package/skills/build-systems/SKILL.md +3 -3
  34. package/skills/graph-execute/SKILL.md +13 -0
  35. package/.claude/rules/archetypes.md +0 -60
@@ -0,0 +1,183 @@
1
+ ---
2
+ paths:
3
+ - '**/services/**/*.ts'
4
+ - '**/*-plugin.ts'
5
+ - '**/*-plugin/**/*.ts'
6
+ - '**/*dependent-state*.ts'
7
+ - '**/*dependent-state*/**/*.ts'
8
+ ---
9
+
10
+ # Observe pattern
11
+
12
+ `Observe<T>` from `@adobe/data/observe` — a subscription function: `(notify: (value: T) => void) => Unobserve`. Callback may be invoked
13
+ synchronously or asynchronously, zero or more times. Returns `Unobserve` to stop observing.
14
+
15
+ **Always use `Observe.*` helpers to create and manipulate observables. Never write raw subscriptions or manual notify wrappers** — the
16
+ standard helpers cover every composition need and keep the code consistent.
17
+
18
+ ---
19
+
20
+ ## Creation helpers
21
+
22
+ | Helper | Usage |
23
+ | ------------------------------------ | -------------------------------------------------- |
24
+ | `Observe.fromConstant(value)` | `Observe<T>` from a constant value |
25
+ | `Observe.fromProperties({ a, b })` | Combine named observables into `Observe<{ a, b }>` |
26
+ | `Observe.fromArray([obs1, obs2])` | Combine array of observables into `Observe<T[]>` |
27
+ | `Observe.fromPromise(() => Promise)` | Lazy; notifies once when promise resolves |
28
+ | `Observe.createState(initial?)` | `[Observe<T>, (value: T) => void]` — mutable state |
29
+
30
+ ```ts
31
+ const constant = Observe.fromConstant(42);
32
+ const combined = Observe.fromProperties({ a: db.observe.resources.a, b: db.observe.resources.b });
33
+ const [count, setCount] = Observe.createState(0);
34
+ ```
35
+
36
+ ---
37
+
38
+ ## Transformation helpers
39
+
40
+ | Helper | Usage |
41
+ | ----------------------------------- | ------------------------------------------------------------ |
42
+ | `Observe.withMap(obs, fn)` | Transform value; map from one type to another |
43
+ | `Observe.withFilter(obs, fn)` | Transform or filter; return `undefined` to skip |
44
+ | `Observe.withDefault(default, obs)` | Use default when value is undefined |
45
+ | `Observe.withLazy(() => obs)` | Defer expensive observable creation until first subscription |
46
+ | `Observe.withUnwrap(obs)` | Flatten `Observe<Observe<T>>` to `Observe<T>` |
47
+
48
+ ```ts
49
+ const doubled = Observe.withMap(count, n => n * 2);
50
+ const max = Observe.withFilter(Observe.fromProperties({ a, b }), ({ a, b }) => Math.max(a, b));
51
+ const withFallback = Observe.withDefault('unknown', maybeName);
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Conversion helpers
57
+
58
+ | Helper | Usage |
59
+ | ------------------------ | ----------------------------------- |
60
+ | `Observe.toPromise(obs)` | Resolve with first value (one-shot) |
61
+
62
+ `Observe.toPromise` is for services reading a current value — **never use it in UI elements**.
63
+
64
+ ---
65
+
66
+ ## ⛔ Antipattern: synchronous "peek"
67
+
68
+ **Never read an observable's current value by subscribing and immediately unsubscribing.** The `notify` callback may fire asynchronously
69
+ (auth state, network, derived chains), so the snapshot is stale or `undefined`.
70
+
71
+ ```ts
72
+ // WRONG — only works if the observable happens to emit synchronously on subscribe
73
+ let value: T | undefined;
74
+ const unobserve = someObservable(v => {
75
+ value = v;
76
+ });
77
+ unobserve();
78
+ // use `value` ← undefined for any async-resolving observable
79
+ ```
80
+
81
+ ```ts
82
+ // CORRECT — resolve the current value explicitly
83
+ const value = await Observe.toPromise(someObservable);
84
+ ```
85
+
86
+ A stored `unobserve` is **only** ever for deferred teardown (returned from an Effect, called on unmount/cleanup) — never to bookend a
87
+ synchronous read. If a sync call site (e.g. a DOM event handler that must call `preventDefault()` before any `await`) needs the value, do
88
+ the synchronous work first, then `void asyncHelper(...)` that awaits `Observe.toPromise`. In a Lit render path, read reactive values through
89
+ `useObservableValues`, not `toPromise`.
90
+
91
+ ---
92
+
93
+ ## ⛔ Antipattern: `undefined` as an Observe data value
94
+
95
+ `@adobe/data/observe` reserves `undefined` to mean **"no value has been emitted yet"** — it gates the synchronous initial emit in
96
+ `Observe.createState`, `fromProperties`, and friends. A data value flowing through an Observe pipeline must use `null` for the absent case,
97
+ **never** `undefined`.
98
+
99
+ ```ts
100
+ // WRONG — Observe<X | undefined>
101
+ const [projectId, setProjectId] = Observe.createState<ProjectId | undefined>(undefined);
102
+ return Observe.withDefault(undefined, cloudSync.projectName);
103
+
104
+ // CORRECT — Observe<X | null>
105
+ const [projectId, setProjectId] = Observe.createState<ProjectId | null>(null);
106
+ return Observe.withDefault<string | null>(null, cloudSync.projectName);
107
+ ```
108
+
109
+ Why it bites:
110
+
111
+ - `createState<T | undefined>(undefined)` does **not** fire on subscribe (the implementation treats `undefined` as "not yet set"), so
112
+ downstream `useObservableValues` callers wait forever for a value that never arrives.
113
+ - `fromProperties` re-emits when an input flips `undefined → defined`, not when a value changes — using `undefined` as data corrupts the
114
+ dirty-tracking.
115
+ - `null` JSON-stringifies across the wire; `undefined` silently disappears.
116
+
117
+ When auditing new code, flag `Observe<… | undefined>`, `createState<… | undefined>`, `withDefault(undefined`, and `fromConstant(undefined)`
118
+ — each should use `null`. (A plain function returning `undefined`, e.g. an early `return` from an async helper, is unrelated and fine.)
119
+
120
+ ---
121
+
122
+ ## ⛔ Antipattern: membership-only subscription for value reads (ECS)
123
+
124
+ `db.observe.select(archetype.components)` fires **only when entities enter or leave the archetype** — i.e. on set-membership changes. It
125
+ does **not** fire when a component value changes on an entity already in the set. If the callback reads component **values**, this
126
+ subscription is almost always wrong: it emits once on load, then silently stops reacting when the user edits values (opacity slider,
127
+ selection toggle, drag end). The derived state goes stale with no error — the hardest bug to catch.
128
+
129
+ Pick the subscription by what the callback reads:
130
+
131
+ | Callback needs | Use |
132
+ | ----------------------------------------------------------------------- | --------------------------------------------------------- |
133
+ | Entity **IDs** only ("how many items," "does this track have children") | `db.observe.select(archetype.components)` |
134
+ | Component **values** (any `db.get(id, 'field')` in a map/filter) | `Database.observeSelectDeep(db, [...fields] as const)` |
135
+ | A single UI element bound to one entity | `db.observe.entity(id)` / `observe.entity(id, archetype)` |
136
+
137
+ ```ts
138
+ // WRONG — reads values but only reacts to add/remove; value edits never re-run this
139
+ Observe.withMap(db.observe.select(archetype.components), ids => ids.filter(id => (db.get(id, 'selectedByUsers') ?? []).includes(userId)));
140
+
141
+ // CORRECT — value-tracking subscription; list exactly the fields read downstream
142
+ const SELECTED_FIELDS = ['selectedByUsers'] as const; // hoist the tuple above the factory
143
+ const rows$ = Database.observeSelectDeep(db, SELECTED_FIELDS);
144
+ Observe.withMap(rows$, rows => rows.filter(r => r.selectedByUsers?.includes(userId)).map(r => r.id));
145
+ ```
146
+
147
+ `observeSelectDeep` re-emits on **any** change to **any** listed component on **any** matched entity — so list only the fields actually
148
+ read, and when the matched set is large pair it with a structural deduplicate downstream so identical-looking emissions don't propagate
149
+ (`@adobe/data/observe`'s `withDeduplicate` is reference-equality only). `observeSelectDeep` from a UI element is banned — let each element
150
+ observe its own entity (see `.claude/rules/ecs.md` and `.claude/rules/squirrel/ui-data-access.md`).
151
+
152
+ ---
153
+
154
+ ## Common patterns
155
+
156
+ **Derived observable from service:**
157
+
158
+ ```ts
159
+ export const isDevelopment = memoize((service: Pick<EnvironmentService, 'type'>) =>
160
+ Observe.withMap(service.type, type => type === 'development')
161
+ );
162
+ ```
163
+
164
+ **Computed from multiple resources:**
165
+
166
+ ```ts
167
+ Observe.withFilter(Observe.fromProperties({ a: db.observe.resources.a, b: db.observe.resources.b }), ({ a, b }) => Math.max(a, b));
168
+ ```
169
+
170
+ **Lazy creation:** `Observe.withLazy(() => expensiveObs)` — defers until first subscription.
171
+
172
+ ---
173
+
174
+ ## Execute
175
+
176
+ When adding or using Observe:
177
+
178
+ 1. Use `Observe.fromConstant` for static values.
179
+ 2. Use `Observe.fromProperties` to combine multiple observables.
180
+ 3. Use `Observe.withFilter` when mapping and/or filtering.
181
+ 4. Use `Observe.createState` for mutable state with a setter.
182
+ 5. Use `Observe.withUnwrap` to flatten `Observe<Observe<T>>`.
183
+ 6. Call `unobserve()` when cleaning up (e.g. component unmount).
@@ -0,0 +1,102 @@
1
+ ---
2
+ paths:
3
+ - '**/*.tsx'
4
+ ---
5
+
6
+ # React component authoring
7
+
8
+ React components live in the `components/` layer. Consume Observe and void actions from plugins. Use `@adobe/data-react` for `useDatabase`,
9
+ `useObservableValues`, and `DatabaseProvider`.
10
+
11
+ ---
12
+
13
+ ## useDatabase — single context
14
+
15
+ Binding components **must** call `useDatabase` to obtain the main service context. Do **not** access any other React context. Additional
16
+ contexts create a context waterfall and hurt performance. All other services and state are reachable from the database.
17
+
18
+ ---
19
+
20
+ ## Binding component vs presentation
21
+
22
+ **Binding component** — the React component that:
23
+
24
+ - Injects observed values via `useObservableValues`
25
+ - Triggers re-render when those values change
26
+ - Binds action callbacks to the presentation
27
+
28
+ **Presentation** — a pure function (no hooks) that receives data and action callbacks as props and returns JSX. Keep reactive logic in the
29
+ binding component; presentation stays pure.
30
+
31
+ ---
32
+
33
+ ## Props from parent
34
+
35
+ **Do not** pass values from parent except when needed to identify which entity in the database to bind to.
36
+
37
+ ```tsx
38
+ // Parent: passes entity so child knows which record to bind to
39
+ {
40
+ values.sprites.map(entity => <Sprite key={entity} entity={entity} />);
41
+ }
42
+
43
+ // Child: uses entity prop to observe the right record
44
+ function Sprite({ entity }: { entity: Entity }) {
45
+ const db = useDatabase();
46
+ const values = useObservableValues(() => ({ sprite: db.observe.entity(entity, db.archetypes.Sprite) }), [entity]);
47
+ }
48
+ ```
49
+
50
+ ---
51
+
52
+ ## useObservableValues
53
+
54
+ _Most_ binding components use a **single** `useObservableValues` call. Collect all observed values in one object. Use `Observe.withDefault`
55
+ for values that may resolve slowly.
56
+
57
+ ```tsx
58
+ function Counter() {
59
+ const db = useDatabase(counterPlugin);
60
+ const values = useObservableValues(() => ({
61
+ count: db.observe.resources.count,
62
+ }));
63
+ if (!values) return null;
64
+ return presentation.render({ ...values, increment: db.transactions.increment });
65
+ }
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Presentation exports
71
+
72
+ Presentation files ONLY export `render` (and localization bundles where appropriate). Nothing else. If you need render args type externally,
73
+ use `Parameters<typeof render>[0]`.
74
+
75
+ ---
76
+
77
+ ## Action callbacks (not events)
78
+
79
+ Use **verb** or **verbNoun** names — never `on*` prefix. See `.claude/rules/presentation.md` for full examples.
80
+
81
+ **Pass function references directly** when the signature matches; wrap in an arrow function only to supply arguments:
82
+
83
+ ```tsx
84
+ increment: db.transactions.increment; // reference — no wrapper needed
85
+ toggleSprite: () => db.transactions.toggleSpriteActive({ entity }); // args required — wrap
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Execute
91
+
92
+ When creating or modifying a React component:
93
+
94
+ 1. Call `useDatabase` for the main service context. Do not use any other React context.
95
+ 2. Split into binding component (reactive) and presentation (pure).
96
+ 3. Use a single `useObservableValues` in the binding component.
97
+ 4. Pass observed values and action callbacks to presentation. Pass function references directly when the signature matches; wrap only when
98
+ supplying arguments.
99
+ 5. Keep presentation pure — no hooks.
100
+ 6. Presentation exports only `render` (and localization bundles where appropriate).
101
+ 7. Add `*-presentation.test.tsx` for presentation when appropriate; do not unit test binding components.
102
+ 8. Never include business logic within binding components. Move into computed values or action handlers.
@@ -0,0 +1,164 @@
1
+ ---
2
+ paths:
3
+ - '**/services/**/*.ts'
4
+ - '**/*-service.ts'
5
+ ---
6
+
7
+ # Service authoring
8
+
9
+ Asynchronous data services. Live in the `services/` layer. Adhere to the namespace rule for type and function organization.
10
+
11
+ **Data** = readonly JSON values or Blobs.
12
+
13
+ ---
14
+
15
+ ## Front-end vs back-end services
16
+
17
+ **Front-end services** — called directly from UI components. Support unidirectional control flow.
18
+
19
+ | Allowed | Not allowed |
20
+ | ------------------------------- | -------------------------------- |
21
+ | `Observe<Data>` | Promise or AsyncGenerator return |
22
+ | Other services (sub-Services) | |
23
+ | Void-returning action functions | |
24
+
25
+ **Back-end services** — usually stateless. Functions typically return `Promise<Data>` or `AsyncGenerator<Data>`. Not called directly from
26
+ UI; front-end services call them.
27
+
28
+ This separation keeps UI components on a strict unidirectional path: data down via void actions, data up via Observe.
29
+
30
+ ---
31
+
32
+ ## Front-end service constraints
33
+
34
+ `Service` interfaces for UI consumption may only contain:
35
+
36
+ - **`Observe<Data>`** — observable properties or factories
37
+ - **Sub-Service**s — nested service interfaces
38
+ - **Action functions** — zero or more `Data` arguments, return `void` only
39
+ - **Factory functions** — create observables or sub-Services
40
+
41
+ Compile-time check: `Assert<AsyncDataService.IsValid<ServiceInterface>>`.
42
+
43
+ ---
44
+
45
+ ## Folder structure and cohesion
46
+
47
+ **MUST NOT** place implementation files alongside the interface file. The `<name>-service/` folder MUST contain only the interface file and
48
+ `public.ts`.
49
+
50
+ ```
51
+ services/<name>-service/
52
+ <name>-service.ts ← interface + contract types; `export * as ServiceName from './public.js'` at bottom
53
+ public.ts ← re-exports factory (and other public exports) from the implementation sub-folder
54
+ <impl-name>/ ← REQUIRED sub-folder for all implementation files
55
+ create-<name>-service.ts ← factory function
56
+ *.ts ← helpers, utils, constants (implementation-private)
57
+ *.test.ts ← tests live here alongside implementation
58
+ ```
59
+
60
+ **Single-file exception**: a bare factory with no helpers, no utilities, and no constants MAY live as a flat file alongside the interface.
61
+ The moment any additional implementation file is needed, the factory MUST move into a sub-folder.
62
+
63
+ **Interface file** (`<name>-service.ts`) — interface declaration and types that define the public service contract. MUST NOT import from any
64
+ implementation file or sub-folder. At the bottom of the file, exports the namespace: `export * as ServiceName from './public.js'`. This
65
+ merges the interface type and the factory namespace under one identifier — callers type against the interface and invoke factory functions
66
+ through the same name.
67
+
68
+ **Separate types file at the interface level** — only permitted if it contains exclusively public-contract types (types that appear in the
69
+ interface's method signatures or `Observe<>` generic arguments). Implementation-detail types — third-party API shapes, `window.*` global
70
+ augmentations, internal utilities — MUST live inside the implementation sub-folder.
71
+
72
+ **`*.test.ts` files MUST NOT appear at the interface folder level.** Every test file belongs inside the implementation sub-folder next to
73
+ the code it tests; there is nothing at the interface level to test.
74
+
75
+ **Implementation sub-folder** — all implementation logic including the factory, utilities, constants, helpers, and their tests. MUST NOT
76
+ depend on the interface file's types via the `public.js` re-export; import directly from `<name>-service.js`.
77
+
78
+ **public.ts** — MUST re-export the factory function and any other public exports from the implementation sub-folder. Does NOT re-export the
79
+ interface — the interface is exported directly from `<name>-service.ts`.
80
+
81
+ **No classes** — factory function or static plain object only. `this` bindings are brittle and block higher-order composition.
82
+
83
+ ---
84
+
85
+ ## Factory export shape — `singletonFactory` + dual export
86
+
87
+ Every service factory MUST be wrapped with `singletonFactory` from `factory-functions.js`. This guarantees one instance per argument tuple
88
+ when the same factory is called multiple times, preventing duplicate subscriptions and torn state.
89
+
90
+ Export **both** the raw `factory` and the singleton-wrapped `create`:
91
+
92
+ ```ts
93
+ // <name>-service/<impl-name>/create-<name>-service.ts
94
+ import { singletonFactory } from 'factory-functions.js';
95
+
96
+ export const factory = (deps: Deps): NameService => {
97
+ // implementation
98
+ };
99
+
100
+ // `factory` — for test injection (bypasses the singleton cache; each test gets a fresh instance)
101
+ // `create` — for production use (singleton per deps-tuple)
102
+ export const create = singletonFactory(factory);
103
+ ```
104
+
105
+ `public.ts` re-exports both:
106
+
107
+ ```ts
108
+ export { create, factory } from './<impl-name>/create-<name>-service.js';
109
+ ```
110
+
111
+ **Why:** Tests must create fresh instances without sharing a singleton cache. Production callers use `create`; tests that need isolation
112
+ import `factory` directly.
113
+
114
+ ---
115
+
116
+ ## Service interface cleanliness
117
+
118
+ The `<name>-service.ts` interface MUST NOT reference internal implementation tools, third-party SDK names, or infrastructure system
119
+ identifiers in any method signature, property name, or type parameter.
120
+
121
+ **Wrong** — leaks an internal infrastructure name into the UI contract:
122
+
123
+ ```ts
124
+ interface SessionService extends Service {
125
+ readonly dunamis: DunamisSdk; // UI now knows Dunamis exists
126
+ }
127
+ ```
128
+
129
+ **Correct** — surfaces only what UI needs, implementation-tool-agnostic:
130
+
131
+ ```ts
132
+ interface SessionService extends Service {
133
+ readonly sessionId: Observe<string>;
134
+ }
135
+ ```
136
+
137
+ The interface is the contract between UI and data layers. If the underlying tool is swapped (Dunamis → another SDK, cookies → localStorage),
138
+ the interface must not change. Any leak of an internal tool name into the interface couples the UI to an implementation detail it must not
139
+ know about.
140
+
141
+ ---
142
+
143
+ ## Execute
144
+
145
+ When creating or modifying a service:
146
+
147
+ 1. Place in `services/<name>-service/`.
148
+ 2. Interface in `<name>-service.ts` — types only, extend `Service`, add `Assert<AsyncDataService.IsValid<>>`.
149
+ 3. All implementation files go inside a named sub-folder (e.g. `create-<name>-service/`).
150
+ 4. At the bottom of `<name>-service.ts`, add `export * as ServiceName from './public.js'`.
151
+ 5. In `public.ts`, re-export the factory (and any other public exports) from the implementation sub-folder.
152
+ 6. For front-end services: actions return void only; observables observe Data or Service only. For back-end services: functions return
153
+ `Promise<Data>` or `AsyncGenerator<Data>`.
154
+
155
+ ---
156
+
157
+ ## Verify
158
+
159
+ When any `src/**/services/**/*.ts` file is in the diff, confirm all three invariants:
160
+
161
+ 1. **Interface/implementation separation** — every `.ts` file added or modified in `<name>-service/` is either `<name>-service.ts`,
162
+ `public.ts`, or inside a named sub-folder. Any file at the root of the folder that is neither is a violation.
163
+ 2. **`public.ts` completeness** — `public.ts` re-exports the factory function (and any other public exports from the sub-folder).
164
+ 3. **Interface file purity** — `<name>-service.ts` contains no imports from implementation sub-folders.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  paths:
3
- - 'packages/**/*.ts'
4
- - 'packages/**/*.tsx'
3
+ - '**/*.ts'
4
+ - '**/*.tsx'
5
5
  ---
6
6
 
7
7
  # Type casts — only two valid uses
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adobe-data-ai",
3
- "version": "0.9.84",
3
+ "version": "0.9.87",
4
4
  "description": "Architecture skills for @adobe/data — data-oriented modelling, archetype iteration, hot-path performance, and related conventions.",
5
5
  "author": {
6
6
  "name": "Adobe"
package/README.md CHANGED
@@ -18,15 +18,22 @@ It installs two things:
18
18
 
19
19
  ## Install — Claude Code
20
20
 
21
- Skills load as a marketplace plugin; rules install into the project.
21
+ Skills load as a marketplace plugin; rules install into the project. Run these
22
+ three from a shell (agent-runnable):
22
23
 
23
24
  ```
24
- /plugin marketplace add adobe/data
25
- /plugin install adobe-data-ai@adobe-data-skills
25
+ claude plugin marketplace add adobe/data --scope project
26
+ claude plugin install adobe-data-ai@adobe-data-skills --scope project
26
27
  npx @adobe/data-ai@latest install
27
28
  ```
28
29
 
29
- - Skills → the `adobe-data-ai` plugin. **Update:** `/plugin` → update `adobe-data-ai`.
30
+ In an interactive Claude session you can instead use the slash-command form of
31
+ the first two — `/plugin marketplace add adobe/data` then
32
+ `/plugin install adobe-data-ai@adobe-data-skills` — but an agent driving a shell
33
+ should use the `claude plugin …` commands above (slash commands aren't
34
+ shell-runnable).
35
+
36
+ - Skills → the `adobe-data-ai` plugin. **Update:** `claude plugin update adobe-data-ai`.
30
37
  - Rules → `.claude/rules/adobe-data-ai/`. **Update:** re-run `npx @adobe/data-ai@latest install`.
31
38
 
32
39
  ## Install — Cursor (and Codex / other `.agents` agents)
@@ -58,3 +65,16 @@ by name when you want a single phase or a specialized flow: `build-data`,
58
65
  `build-ui`, `build-app-entry`; plus `build-game`, `meta-build` (phase-by-phase
59
66
  in subagents), `review` (audit output against the rules), and `structure`
60
67
  (reason about layout).
68
+
69
+ ## One-liner: install + build an app
70
+
71
+ Paste one of these into a shell, replacing the quoted text with your own app
72
+ description. It installs the bundle, then runs `build-application` headlessly on
73
+ your prompt.
74
+
75
+ This presumes you are already running the agent within the directory you want to use as your project root.
76
+
77
+ ```
78
+ Find the @adobe/data-ai package on npmjs and then install it in the current directory.
79
+ Next use it's /build-application skill to build <prompt here>
80
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/data-ai",
3
- "version": "0.9.84",
3
+ "version": "0.9.87",
4
4
  "description": "Cross-agent architecture skills for @adobe/data — installable as a Claude Code plugin or copied into any Agent-Skills-compatible agent (Cursor, Codex).",
5
5
  "type": "module",
6
6
  "private": false,
@@ -29,12 +29,12 @@
29
29
  "skills"
30
30
  ],
31
31
  "peerDependencies": {
32
- "@adobe/data": "0.9.84"
32
+ "@adobe/data": "0.9.87"
33
33
  },
34
34
  "devDependencies": {
35
35
  "typescript": "^5.8.3",
36
36
  "vitest": "^1.6.0",
37
- "@adobe/data": "0.9.84"
37
+ "@adobe/data": "0.9.87"
38
38
  },
39
39
  "license": "MIT",
40
40
  "scripts": {
@@ -1,10 +1,12 @@
1
1
  ---
2
2
  name: build-actions
3
- description: Build a feature's ecs action-database — async orchestration over services and transactions. If the feature has async flows.
3
+ description: Build a feature's ecs action-database — async orchestration over services and transactions. If the feature has async flows or side effects outside of the main store.
4
4
  input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
+ Skip if this feature doesn't contain or require actions.
9
+
8
10
  Create `ecs/action-database/`: `action-database.ts` (extends `ServiceDatabase`, adds `actions`
9
11
  from `./actions/index.js`) plus an `actions/` folder — one async orchestrator per file. An action
10
12
  takes `db: ServiceDatabase`, awaits `services/` ports, then commits **exactly one** transaction.
@@ -5,6 +5,8 @@ input: app
5
5
  output: app
6
6
  ---
7
7
 
8
- /build-feature (base — features/main)
9
- |> /build-feature (each peer feature)
10
- |> /build-app-entry
8
+ /graph-execute
9
+ /build-feature (base — features/main)
10
+ for each sub feature
11
+ /build-feature
12
+ /build-app-entry
@@ -5,6 +5,8 @@ input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
+ Skip if this feature doesn't contain or require computed values.
9
+
8
10
  Create `ecs/computed-database/`: `computed-database.ts` (extends the previous layer, adds
9
11
  `computed` from `./computed/index.js`) plus a `computed/` folder — one `cached` derived value
10
12
  per file + barrel. Derivation logic lives in `data/`; a computed only wires a db observable to it.
@@ -11,6 +11,10 @@ and other `data/` declarations):
11
11
  - one namespace folder per data type — `<type>/{<type>.ts, schema.ts, public.ts, <helper>.ts}`;
12
12
  - `data/state/` — the `State` aggregate plus its pure transforms/derivations.
13
13
 
14
+ **Always create `data/state/`** — even for a shell feature whose only field is a
15
+ boolean or enum. A feature with ECS resources/transactions but no `State` is
16
+ incomplete: the transaction must wrap a pure transform, not invent the logic. In rare circumstances, there may be no real state in a feature in which case the state type = {}
17
+
14
18
  The schema is the single source of truth; the type derives from it (`Schema.ToType`).
15
19
  Helpers are pure and unit-tested. Run this first — every other layer imports `data/`.
16
20
 
@@ -1,21 +1,18 @@
1
1
  ---
2
2
  name: build-feature
3
- description: Build a complete feature by piping the per-layer build-* skills bottom-up.
3
+ description: Build a complete feature step by step
4
4
  input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
- /build-data
9
- |> /build-services
10
- |> /build-core-database
11
- |> /build-indexes
12
- |> /build-transactions
13
- |> /build-computed
14
- |> /build-systems
15
- |> /build-service-database
16
- |> /build-actions
17
- |> /build-ui
18
-
19
- Run with /x-execute. Each step is a no-op for a layer the feature doesn't use
20
- (`build-systems` only for real-time features; `build-services` / `build-service-database` /
21
- `build-actions` only when the feature has async flows).
8
+ /graph-execute
9
+ /build-data
10
+ /build-services
11
+ /build-core-database
12
+ /build-indexes
13
+ /build-transactions
14
+ /build-computed
15
+ /build-service-database
16
+ /build-actions
17
+ /build-systems
18
+ /build-ui
@@ -15,5 +15,7 @@ transactions = moves/spawns, computed = score/status/winner, ui = view + input.
15
15
  (see data-lit-tictactoe).
16
16
  - **Real-time** adds the `build-systems` phase: a per-frame tick loop (movement,
17
17
  collision, lifetime) driven by the scheduler, over many entities in distinct
18
- archetypes. This is where components, archetypes, indexes, and systems all
19
- carry real weight — see `features/ecs/systems.md`.
18
+ archetypes. Systems must stay O(1) per touched entity — never project the whole
19
+ `State` into/out of the store each frame (see `features/ecs/systems.md`).
20
+ This is where components, archetypes, indexes, and systems all carry real
21
+ weight.
@@ -5,6 +5,8 @@ input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
+ Skip if this feature doesn't contain or require indexes.
9
+
8
10
  Create `ecs/index-database/`: `index-database.ts` (extends `CoreDatabase`, adds the `indexes`
9
11
  facet from `./indexes/index.js`) plus an `indexes/` folder — one index per file + an `index.ts`
10
12
  barrel.
@@ -5,6 +5,8 @@ input: feature
5
5
  output: feature
6
6
  ---
7
7
 
8
+ Skip if this feature doesn't contain or require services.
9
+
8
10
  Create the feature's `services/` layer: one `<name>-service/` namespace folder per async
9
11
  contract — an `interface` (validated with `AsyncDataService.IsValid`), async-only members,
10
12
  and `create*` factories.