@dxos/plugin-game 0.12.0-next-73daef452f1464b62efdbca75fb0067c1066f264 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PLUGIN.mdl +62 -10
- package/dist/lib/Game.mjs +4 -5
- package/dist/lib/Game.mjs.map +1 -1
- package/dist/lib/GameCapabilities.mjs.map +1 -1
- package/dist/lib/GameUtil.mjs +2 -7
- package/dist/lib/GameUtil.mjs.map +1 -1
- package/dist/lib/capabilities.browser.mjs +28 -0
- package/dist/lib/capabilities.browser.mjs.map +1 -0
- package/dist/lib/capabilities.mjs +7 -5
- package/dist/lib/capabilities.mjs.map +1 -1
- package/dist/lib/capabilities.node.mjs +5 -1
- package/dist/lib/capabilities.node.mjs.map +1 -1
- package/dist/lib/capabilities.tauri.mjs +28 -0
- package/dist/lib/capabilities.tauri.mjs.map +1 -0
- package/dist/lib/capabilities.workerd.mjs.map +1 -1
- package/dist/lib/chunk-GameArticle.mjs +5 -5
- package/dist/lib/chunk-GameArticle.mjs.map +1 -1
- package/dist/lib/chunk-GameCard.mjs +3 -3
- package/dist/lib/chunk-GameCard.mjs.map +1 -1
- package/dist/lib/chunk-PLUGIN.mjs +6 -0
- package/dist/lib/chunk-PLUGIN.mjs.map +1 -0
- package/dist/lib/chunk-create-object.mjs +3 -3
- package/dist/lib/chunk-create-object.mjs.map +1 -1
- package/dist/lib/chunk-react-surface.mjs +3 -3
- package/dist/lib/chunk-react-surface.mjs.map +1 -1
- package/dist/lib/components.mjs +50 -30
- package/dist/lib/components.mjs.map +1 -1
- package/dist/lib/containers.mjs.map +1 -1
- package/dist/lib/index.mjs +2 -3
- package/dist/lib/meta.mjs.map +1 -1
- package/dist/lib/ns/GameUtil.mjs +11 -0
- package/dist/lib/ns/GameUtil.mjs.map +1 -0
- package/dist/lib/translations.mjs +2 -1
- package/dist/lib/translations.mjs.map +1 -1
- package/dist/lib/util.mjs +2 -2
- package/dist/types/src/GameUtil.d.ts +2 -0
- package/dist/types/src/GameUtil.d.ts.map +1 -0
- package/dist/types/src/capabilities/gen/browser.d.ts +6 -0
- package/dist/types/src/capabilities/gen/browser.d.ts.map +1 -0
- package/dist/types/src/capabilities/gen/node.d.ts.map +1 -1
- package/dist/types/src/capabilities/gen/tauri.d.ts +6 -0
- package/dist/types/src/capabilities/gen/tauri.d.ts.map +1 -0
- package/dist/types/src/capabilities/gen/workerd.d.ts.map +1 -1
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/components/CreateGamePanel.d.ts +4 -3
- package/dist/types/src/components/CreateGamePanel.d.ts.map +1 -1
- package/dist/types/src/components/CreateGamePanel.stories.d.ts +2 -0
- package/dist/types/src/components/CreateGamePanel.stories.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +1 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/containers/GameArticle/GameArticle.d.ts +1 -1
- package/dist/types/src/containers/GameArticle/GameArticle.d.ts.map +1 -1
- package/dist/types/src/containers/GameArticle/index.d.ts +1 -1
- package/dist/types/src/containers/GameArticle/index.d.ts.map +1 -1
- package/dist/types/src/containers/GameCard/GameCard.d.ts +1 -1
- package/dist/types/src/containers/GameCard/GameCard.d.ts.map +1 -1
- package/dist/types/src/containers/GameCard/index.d.ts +1 -1
- package/dist/types/src/containers/GameCard/index.d.ts.map +1 -1
- package/dist/types/src/containers/index.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +2 -3
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +2 -0
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/Game.d.ts.map +1 -1
- package/dist/types/src/types/GameCapabilities.d.ts +1 -1
- package/dist/types/src/types/GameCapabilities.d.ts.map +1 -1
- package/dist/types/src/types/index.d.ts +3 -3
- package/dist/types/src/types/index.d.ts.map +1 -1
- package/dist/types/src/util/index.d.ts +1 -2
- package/dist/types/src/util/index.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +32 -26
- package/src/GameUtil.ts +7 -0
- package/src/capabilities/create-object.ts +5 -1
- package/src/capabilities/gen/browser.ts +36 -0
- package/src/capabilities/gen/node.ts +2 -2
- package/src/capabilities/gen/tauri.ts +36 -0
- package/src/capabilities/gen/workerd.ts +1 -1
- package/src/capabilities/index.ts +5 -5
- package/src/capabilities/react-surface.ts +2 -2
- package/src/components/CreateGamePanel.stories.tsx +21 -23
- package/src/components/CreateGamePanel.tsx +63 -42
- package/src/components/index.ts +1 -1
- package/src/containers/GameArticle/GameArticle.tsx +5 -5
- package/src/containers/GameArticle/index.ts +1 -1
- package/src/containers/GameCard/GameCard.tsx +3 -3
- package/src/containers/GameCard/index.ts +1 -1
- package/src/containers/index.ts +2 -2
- package/src/index.ts +2 -3
- package/src/meta.ts +1 -1
- package/src/translations.ts +3 -2
- package/src/types/Game.ts +4 -4
- package/src/types/GameCapabilities.ts +1 -1
- package/src/types/index.ts +3 -3
- package/src/util/index.ts +1 -2
package/PLUGIN.mdl
CHANGED
|
@@ -18,9 +18,10 @@ Each extension is defined in the Appendix or resolved via its URI.
|
|
|
18
18
|
|-------------|--------------------------------|
|
|
19
19
|
| `type` | `org.dxos.mdl.type@1.0` |
|
|
20
20
|
| `feat` | `org.dxos.mdl.feat@1.0` |
|
|
21
|
-
| `
|
|
21
|
+
| `scenario` | `org.dxos.mdl.scenario@1.0` |
|
|
22
22
|
| `component` | `org.dxos.mdl.component@1.0` |
|
|
23
23
|
| `op` | `org.dxos.mdl.op@1.0` |
|
|
24
|
+
| `test` | `org.dxos.mdl.test@1.0` |
|
|
24
25
|
|
|
25
26
|
## Types
|
|
26
27
|
|
|
@@ -206,7 +207,7 @@ feat F-3: Game Article / Card Rendering
|
|
|
206
207
|
## Acceptance
|
|
207
208
|
|
|
208
209
|
```mdl
|
|
209
|
-
|
|
210
|
+
scenario T-1: Variant picker shows all contributions
|
|
210
211
|
given: two variant plugins have contributed GameVariants
|
|
211
212
|
when: the user opens the create-game panel
|
|
212
213
|
then:
|
|
@@ -215,7 +216,7 @@ test T-1: Variant picker shows all contributions
|
|
|
215
216
|
```
|
|
216
217
|
|
|
217
218
|
```mdl
|
|
218
|
-
|
|
219
|
+
scenario T-2: Create game without form
|
|
219
220
|
given: a variant with no inputSchema is selected
|
|
220
221
|
when: user clicks the variant item
|
|
221
222
|
then:
|
|
@@ -224,7 +225,7 @@ test T-2: Create game without form
|
|
|
224
225
|
```
|
|
225
226
|
|
|
226
227
|
```mdl
|
|
227
|
-
|
|
228
|
+
scenario T-3: Create game with form
|
|
228
229
|
given: a variant with an inputSchema is selected
|
|
229
230
|
when: user fills and submits the form
|
|
230
231
|
then:
|
|
@@ -232,7 +233,7 @@ test T-3: Create game with form
|
|
|
232
233
|
```
|
|
233
234
|
|
|
234
235
|
```mdl
|
|
235
|
-
|
|
236
|
+
scenario T-4: Atomic game creation
|
|
236
237
|
given: a valid variantId and target space
|
|
237
238
|
when: op:createGame is invoked
|
|
238
239
|
then:
|
|
@@ -242,7 +243,7 @@ test T-4: Atomic game creation
|
|
|
242
243
|
```
|
|
243
244
|
|
|
244
245
|
```mdl
|
|
245
|
-
|
|
246
|
+
scenario T-5: Rollback on second write failure
|
|
246
247
|
given: variant state is successfully written
|
|
247
248
|
when: the second write (Game object) fails
|
|
248
249
|
then:
|
|
@@ -251,14 +252,14 @@ test T-5: Rollback on second write failure
|
|
|
251
252
|
```
|
|
252
253
|
|
|
253
254
|
```mdl
|
|
254
|
-
|
|
255
|
+
scenario T-6: Unknown variant rejected
|
|
255
256
|
given: op:createGame is invoked with an unregistered variantId
|
|
256
257
|
when: the handler runs
|
|
257
258
|
then: UnknownVariant error is returned; no ECHO writes occur
|
|
258
259
|
```
|
|
259
260
|
|
|
260
261
|
```mdl
|
|
261
|
-
|
|
262
|
+
scenario T-7: Unsupported variant fallback
|
|
262
263
|
given: a Game whose variant typename has no registered GameVariant
|
|
263
264
|
when: GameArticle renders
|
|
264
265
|
then: "Unsupported game variant" message is shown instead of crashing
|
|
@@ -266,6 +267,57 @@ test T-7: Unsupported variant fallback
|
|
|
266
267
|
|
|
267
268
|
---
|
|
268
269
|
|
|
270
|
+
## QA
|
|
271
|
+
|
|
272
|
+
Executable tests. Each step carries a human instruction (`do`) and, where the step maps to an
|
|
273
|
+
invocable operation, an agent binding (`invoke`). Report failures by test and step number.
|
|
274
|
+
|
|
275
|
+
```mdl
|
|
276
|
+
test QA-1: Create a game through the variant picker and render it
|
|
277
|
+
desc: |
|
|
278
|
+
The generic host round-trip — pick a registered variant, create the Game wrapper plus its
|
|
279
|
+
hidden variant state, and confirm dispatch renders the variant's article. Uses
|
|
280
|
+
plugin-chess's variant since it is the only one shipped and enabled by default; the test
|
|
281
|
+
tests plugin-game's dispatch, not chess's own gameplay (see plugin-chess's QA for that).
|
|
282
|
+
actors: human
|
|
283
|
+
covers: [F-1.2, F-2.1, F-2.4, F-3.1, T-2, T-4]
|
|
284
|
+
status: unverified
|
|
285
|
+
given:
|
|
286
|
+
- an identity exists
|
|
287
|
+
- space: a space is open
|
|
288
|
+
- collection: a collection in that space, to create into
|
|
289
|
+
- plugin-chess (or another GameVariant contributor) is enabled, so the picker lists at least one variant
|
|
290
|
+
- no game named "QA Game" exists — ABORT rather than deleting one this test did not create
|
|
291
|
+
- game: (bound during the run, not beforehand) the Game object the "Select the Chess variant" step creates — recorded by the tester, since no invoke captures it
|
|
292
|
+
steps:
|
|
293
|
+
- name: Open the create-game panel
|
|
294
|
+
do: In the navtree, click + on a collection and choose Game.
|
|
295
|
+
expect: CreateGamePanel opens showing a searchable list including a "Chess" row
|
|
296
|
+
note: |
|
|
297
|
+
NO `invoke` — `createGame` is registered as a `CreateObjectEntry.customPanel`, not an
|
|
298
|
+
`Operation.make` with a runtime key, so there is no key to invoke through the operation
|
|
299
|
+
invoker; opening the panel is itself the create-object gesture (see `code-style`/`operations`
|
|
300
|
+
conventions: only `Operation.make` instances get a DXN, and this factory is not one).
|
|
301
|
+
- name: Select the Chess variant
|
|
302
|
+
do: Click the "Chess" row (it has no inputSchema, so no form stage follows).
|
|
303
|
+
expect: a Game object appears under the collection, named after the default, with hidden chess variant state referenced by `game.variant`
|
|
304
|
+
note: |
|
|
305
|
+
NO `invoke` for the same reason as the previous step — this whole creation gesture is
|
|
306
|
+
human-only until `createGame` (or an equivalent) is exposed as an invocable operation.
|
|
307
|
+
Because nothing here is scriptable, there is no captured result to thread forward; the
|
|
308
|
+
tester notes the new Game's id manually for the `after` stage below.
|
|
309
|
+
- name: Open the game and confirm dispatch
|
|
310
|
+
do: Click the Game row in the navtree.
|
|
311
|
+
expect: GameArticle resolves `game.variant` to the chess state object and delegates to chess's own article — a starting chess position renders, not the "Unsupported game variant" fallback
|
|
312
|
+
after:
|
|
313
|
+
- name: Delete the game
|
|
314
|
+
do: Right-click the Game row in the navtree and choose Delete.
|
|
315
|
+
invoke: org.dxos.operation.space.removeObjects { objects: [$given.game], target: $given.collection }
|
|
316
|
+
expect: the Game row leaves the navtree, its plank closes if open, and the hidden variant state object is also removed
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
269
321
|
## Appendix: Extension Definitions
|
|
270
322
|
|
|
271
323
|
Extension block types used in this document are defined below using
|
|
@@ -293,8 +345,8 @@ ext feat
|
|
|
293
345
|
```
|
|
294
346
|
|
|
295
347
|
```mdl
|
|
296
|
-
ext
|
|
297
|
-
uri: org.dxos.mdl.
|
|
348
|
+
ext scenario
|
|
349
|
+
uri: org.dxos.mdl.scenario@1.0
|
|
298
350
|
desc: An acceptance scenario expressed as given / when / then steps.
|
|
299
351
|
fields:
|
|
300
352
|
given?: Step | Step[]
|
package/dist/lib/Game.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-rolldown-runtime.mjs";
|
|
2
2
|
import { Annotation, DXN, Obj, Ref, Type } from "@dxos/echo";
|
|
3
3
|
import * as Schema from "effect/Schema";
|
|
4
|
-
import { FormInputAnnotation, LabelAnnotation } from "@dxos/echo/Annotation";
|
|
5
4
|
import { CardAnnotation } from "@dxos/schema";
|
|
6
5
|
//#region src/types/Game.ts
|
|
7
6
|
var Game_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -28,12 +27,12 @@ var Player = Schema.Struct({
|
|
|
28
27
|
*/
|
|
29
28
|
var Game = class extends Type.makeObject(DXN.make("org.dxos.type.game", "0.1.0"))(Schema.Struct({
|
|
30
29
|
name: Schema.optional(Schema.String),
|
|
31
|
-
players: Schema.mutable(Schema.Array(Player)).annotate({ description: "Players in the game." }).pipe(FormInputAnnotation.set(false), Schema.optional),
|
|
32
|
-
variant: Ref.Ref(Obj.Unknown).annotate({ description: "Reference to variant-specific state object." }).pipe(FormInputAnnotation.set(false))
|
|
33
|
-
}).pipe(LabelAnnotation.set(["name"]), Annotation.IconAnnotation.set({
|
|
30
|
+
players: Schema.mutable(Schema.Array(Player)).annotate({ description: "Players in the game." }).pipe(Annotation.FormInputAnnotation.set(false), Schema.optional),
|
|
31
|
+
variant: Ref.Ref(Obj.Unknown).annotate({ description: "Reference to variant-specific state object." }).pipe(Annotation.FormInputAnnotation.set(false))
|
|
32
|
+
}).pipe(Annotation.LabelAnnotation.set(["name"]), Annotation.IconAnnotation.set({
|
|
34
33
|
icon: "ph--sword--regular",
|
|
35
34
|
hue: "indigo"
|
|
36
|
-
}), CardAnnotation.set(true), Annotation.IconFromRefAnnotation.set("variant"))) {};
|
|
35
|
+
}), CardAnnotation.set(true), Annotation.IconFromRefAnnotation.set("variant"), Annotation.UserType.set())) {};
|
|
37
36
|
var GameRef = (_variantType) => Ref.Ref(Game);
|
|
38
37
|
/**
|
|
39
38
|
* Build a base `Game` object referencing the given variant-state ECHO object.
|
package/dist/lib/Game.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Game.mjs","names":[],"sources":["../../src/types/Game.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Schema from 'effect/Schema';\n\nimport { Annotation, DXN, Obj, Ref, Type } from '@dxos/echo';\nimport {
|
|
1
|
+
{"version":3,"file":"Game.mjs","names":[],"sources":["../../src/types/Game.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Schema from 'effect/Schema';\n\nimport { Annotation, DXN, Obj, Ref, Type } from '@dxos/echo';\nimport { CardAnnotation } from '@dxos/schema';\n\n// @import-as-namespace\n\n/**\n * A player in a game.\n * `role` is variant-defined (e.g. 'white' | 'black' for chess, 'x' | 'o' for tictactoe).\n * `identity` is the optional DID of a human player; absent for AI or empty seats.\n * `name` is an optional display label (useful for AI/guest players).\n */\nexport const Player = Schema.Struct({\n role: Schema.String.annotate({\n description: 'Variant-defined player role (e.g. \"white\", \"x\").',\n }),\n identity: Schema.optional(\n Schema.String.annotate({\n description: 'DID of the player; absent for AI or empty seats.',\n }),\n ),\n name: Schema.optional(\n Schema.String.annotate({\n description: 'Optional display name for the player.',\n }),\n ),\n});\n\nexport type Player = Schema.Schema.Type<typeof Player>;\n\n/**\n * Base Game object. Variant-specific state lives in a separate referenced object.\n * Unifies all game variants (chess, tic-tac-toe, ...) under one typename so they share\n * graph node, create flow, and shared surface scaffolding.\n */\nexport class Game extends Type.makeObject<Game>(DXN.make('org.dxos.type.game', '0.1.0'))(\n Schema.Struct({\n name: Schema.optional(Schema.String),\n players: Schema.mutable(Schema.Array(Player))\n .annotate({ description: 'Players in the game.' })\n .pipe(Annotation.FormInputAnnotation.set(false), Schema.optional),\n variant: Ref.Ref(Obj.Unknown)\n .annotate({ description: 'Reference to variant-specific state object.' })\n .pipe(Annotation.FormInputAnnotation.set(false)),\n }).pipe(\n Annotation.LabelAnnotation.set(['name']),\n Annotation.IconAnnotation.set({ icon: 'ph--sword--regular', hue: 'indigo' }),\n CardAnnotation.set(true),\n // Delegate the graph-node icon to the referenced variant state's schema. Falls back to\n // the static `ph--sword--regular` above while the variant ref is still loading.\n Annotation.IconFromRefAnnotation.set('variant'),\n Annotation.UserType.set(),\n ),\n) {}\n\n/**\n * Variant-narrowed reference to a Game.\n *\n * Encodes — at the type level — that the variant ref points to a specific state type.\n * Runtime narrowing is performed by `GameUtil.loadGame(ref, variantType)` which validates and\n * returns the resolved Game together with its typed variant state.\n *\n * @example\n * ```ts\n * input: Schema.Struct({\n * game: GameRef(Chess.State),\n * }),\n * // handler:\n * const { game, variant } = yield* GameUtil.loadGame(input.game, Chess.State);\n * // `variant` is typed as Chess.State.\n * ```\n */\nexport type GameRef<_V> = Ref.Ref<Game>;\n\nexport const GameRef = <S extends Type.AnyObj>(_variantType: S) =>\n Ref.Ref(Game) as unknown as Schema.Codec<GameRef<Type.InstanceType<S>>, any>;\n\n/**\n * Build a base `Game` object referencing the given variant-state ECHO object.\n *\n * The variant is stored as a `Ref` so the underlying state (e.g. `Chess.State`,\n * `TicTacToe.State`) lives as its own ECHO object alongside the Game.\n *\n * @param name Optional display name shown in the graph node and Properties form.\n * @param variant Variant-specific state object (e.g. `Chess.State`, `TicTacToe.State`).\n * @param players Optional initial players; copied into a mutable array on the Game.\n * @returns A new `Game` object with `variant` stored as a Ref.\n */\nexport const make = ({\n name,\n players,\n variant,\n}: {\n name?: string;\n players?: ReadonlyArray<Player>;\n variant: Obj.Unknown;\n}): Game => {\n return Obj.make(Game, {\n name,\n players: players ? [...players] : [],\n variant: Ref.make(variant),\n });\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AAiBA,IAAa,SAAS,OAAO,OAAO;CAClC,MAAM,OAAO,OAAO,SAAS,EAC3B,aAAa,uDACf,CAAC;CACD,UAAU,OAAO,SACf,OAAO,OAAO,SAAS,EACrB,aAAa,mDACf,CAAC,CACH;CACA,MAAM,OAAO,SACX,OAAO,OAAO,SAAS,EACrB,aAAa,wCACf,CAAC,CACH;AACF,CAAC;;;;;;AASD,IAAa,OAAb,cAA0B,KAAK,WAAiB,IAAI,KAAK,sBAAsB,OAAO,CAAC,CAAC,CACtF,OAAO,OAAO;CACZ,MAAM,OAAO,SAAS,OAAO,MAAM;CACnC,SAAS,OAAO,QAAQ,OAAO,MAAM,MAAM,CAAC,CAAA,CACzC,SAAS,EAAE,aAAa,uBAAuB,CAAC,CAAA,CAChD,KAAK,WAAW,oBAAoB,IAAI,KAAK,GAAG,OAAO,QAAQ;CAClE,SAAS,IAAI,IAAI,IAAI,OAAO,CAAA,CACzB,SAAS,EAAE,aAAa,8CAA8C,CAAC,CAAA,CACvE,KAAK,WAAW,oBAAoB,IAAI,KAAK,CAAC;AACnD,CAAC,CAAC,CAAC,KACD,WAAW,gBAAgB,IAAI,CAAC,MAAM,CAAC,GACvC,WAAW,eAAe,IAAI;CAAE,MAAM;CAAsB,KAAK;AAAS,CAAC,GAC3E,eAAe,IAAI,IAAI,GAGvB,WAAW,sBAAsB,IAAI,SAAS,GAC9C,WAAW,SAAS,IAAI,CAC1B,CACF,CAAC,CAAC,CAAC;AAqBH,IAAa,WAAkC,iBAC7C,IAAI,IAAI,IAAI;;;;;;;;;;;;AAad,IAAa,QAAQ,EACnB,MACA,SACA,cAKU;CACV,OAAO,IAAI,KAAK,MAAM;EACpB;EACA,SAAS,UAAU,CAAC,GAAG,OAAO,IAAI,CAAC;EACnC,SAAS,IAAI,KAAK,OAAO;CAC3B,CAAC;AACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GameCapabilities.mjs","names":[],"sources":["../../src/types/GameCapabilities.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport type * as Effect from 'effect/Effect';\nimport type * as Schema from 'effect/Schema';\nimport type { ComponentType } from 'react';\n\nimport * as Capability from '@dxos/app-framework/Capability';\nimport type { Database, Obj, Type } from '@dxos/echo';\n\nimport { meta } from '#meta';\n\nimport * as Game from './Game';\n\n/**\n * A game variant contribution. Each variant plugin (chess, tic-tac-toe, ...)\n * contributes one of these via `Capability.contribute(GameCapabilities.VariantProvider, variant)`.\n * Multi capability: more than one variant plugin provides it. Consumers iterate via\n * `Capability.getAll(GameCapabilities.VariantProvider)` (Effect) or\n * `useCapabilities(GameCapabilities.VariantProvider)` (React).\n */\nexport const VariantProvider = Capability.make<GameVariant>()(`${meta.profile.key}.capability.variant`);\n\n/**\n * Contribution from a variant plugin (e.g. plugin-chess).\n * Defines how a game variant is created, rendered, and the roles its players take.\n */\nexport type GameVariant = {\n /** Stable id, typically the variant state typename (e.g. 'org.dxos.type.chess.state'). */\n id: string;\n /** Human-readable variant name (e.g. 'Chess'). */\n label: string;\n /** Optional Phosphor icon name (e.g. 'ph--shield-chevron--regular'). */\n icon?: string;\n /** ECHO Type entity of the variant state object referenced by `Game.variant`. */\n variantType: Type.AnyObj;\n /**\n * Optional Effect Schema rendered as a form after the user picks the variant.\n * To use a `Type.Type` entity, extract its schema first via `Type.getSchema(...)`.\n */\n inputSchema?: Schema.Codec<any, any>;\n /** Roles a player may take in this variant (e.g. ['white', 'black']). */\n roles: readonly string[];\n /**\n * Build the variant state object from the user's form input. May allocate ECHO\n * objects, run effects, etc. Returned object is added to the database alongside the Game.\n */\n createVariant: (input: Record<string, any>) => Effect.Effect<Obj.Any, Error, Database.Service>;\n /** Optional Card surface component for this variant. */\n card?: ComponentType<GameVariantSurfaceProps>;\n /** Optional Article/Section surface component for this variant. */\n article?: ComponentType<GameVariantSurfaceProps>;\n};\n\nexport type GameVariantSurfaceProps = {\n /** The base Game object (may be a snapshot from useObject/Surface). */\n game: Obj.Snapshot<Game.Game> | Game.Game;\n /**\n * The resolved variant state object, live rather than a snapshot so variants can write to it.\n * Reads must subscribe via `useObject(variant, prop)` — the host re-renders only when the ref\n * resolves, not on every mutation.\n */\n variant: Obj.Unknown;\n /** Surface role passed through from the host. */\n role?: string;\n /** Attendable id passed through from the host. */\n attendableId?: string;\n};\n"],"mappings":";;;;;;;;;;;;AAsBA,IAAa,kBAAkB,WAAW,KAAkB,CAAC,CAAC,GAAG,KAAK,QAAQ,IAAI,oBAAoB"}
|
|
1
|
+
{"version":3,"file":"GameCapabilities.mjs","names":[],"sources":["../../src/types/GameCapabilities.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport type * as Effect from 'effect/Effect';\nimport type * as Schema from 'effect/Schema';\nimport type { ComponentType } from 'react';\n\nimport * as Capability from '@dxos/app-framework/Capability';\nimport type { Database, Obj, Type } from '@dxos/echo';\n\nimport { meta } from '#meta';\n\nimport * as Game from './Game.ts';\n\n/**\n * A game variant contribution. Each variant plugin (chess, tic-tac-toe, ...)\n * contributes one of these via `Capability.contribute(GameCapabilities.VariantProvider, variant)`.\n * Multi capability: more than one variant plugin provides it. Consumers iterate via\n * `Capability.getAll(GameCapabilities.VariantProvider)` (Effect) or\n * `useCapabilities(GameCapabilities.VariantProvider)` (React).\n */\nexport const VariantProvider = Capability.make<GameVariant>()(`${meta.profile.key}.capability.variant`);\n\n/**\n * Contribution from a variant plugin (e.g. plugin-chess).\n * Defines how a game variant is created, rendered, and the roles its players take.\n */\nexport type GameVariant = {\n /** Stable id, typically the variant state typename (e.g. 'org.dxos.type.chess.state'). */\n id: string;\n /** Human-readable variant name (e.g. 'Chess'). */\n label: string;\n /** Optional Phosphor icon name (e.g. 'ph--shield-chevron--regular'). */\n icon?: string;\n /** ECHO Type entity of the variant state object referenced by `Game.variant`. */\n variantType: Type.AnyObj;\n /**\n * Optional Effect Schema rendered as a form after the user picks the variant.\n * To use a `Type.Type` entity, extract its schema first via `Type.getSchema(...)`.\n */\n inputSchema?: Schema.Codec<any, any>;\n /** Roles a player may take in this variant (e.g. ['white', 'black']). */\n roles: readonly string[];\n /**\n * Build the variant state object from the user's form input. May allocate ECHO\n * objects, run effects, etc. Returned object is added to the database alongside the Game.\n */\n createVariant: (input: Record<string, any>) => Effect.Effect<Obj.Any, Error, Database.Service>;\n /** Optional Card surface component for this variant. */\n card?: ComponentType<GameVariantSurfaceProps>;\n /** Optional Article/Section surface component for this variant. */\n article?: ComponentType<GameVariantSurfaceProps>;\n};\n\nexport type GameVariantSurfaceProps = {\n /** The base Game object (may be a snapshot from useObject/Surface). */\n game: Obj.Snapshot<Game.Game> | Game.Game;\n /**\n * The resolved variant state object, live rather than a snapshot so variants can write to it.\n * Reads must subscribe via `useObject(variant, prop)` — the host re-renders only when the ref\n * resolves, not on every mutation.\n */\n variant: Obj.Unknown;\n /** Surface role passed through from the host. */\n role?: string;\n /** Attendable id passed through from the host. */\n attendableId?: string;\n};\n"],"mappings":";;;;;;;;;;;;AAsBA,IAAa,kBAAkB,WAAW,KAAkB,CAAC,CAAC,GAAG,KAAK,QAAQ,IAAI,oBAAoB"}
|
package/dist/lib/GameUtil.mjs
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import { t as __exportAll } from "./chunk-rolldown-runtime.mjs";
|
|
2
|
-
import { Database, Obj, Type } from "@dxos/echo";
|
|
3
1
|
import * as Effect from "effect/Effect";
|
|
2
|
+
import { Database, Obj, Type } from "@dxos/echo";
|
|
4
3
|
//#region src/util/load-game.ts
|
|
5
|
-
var load_game_exports = /* @__PURE__ */ __exportAll({
|
|
6
|
-
GameVariantMismatchError: () => GameVariantMismatchError,
|
|
7
|
-
loadGame: () => loadGame
|
|
8
|
-
});
|
|
9
4
|
var GameVariantMismatchError = class extends Error {
|
|
10
5
|
gameId;
|
|
11
6
|
expectedTypename;
|
|
@@ -38,6 +33,6 @@ var loadGame = (ref, variantType) => Effect.gen(function* () {
|
|
|
38
33
|
};
|
|
39
34
|
});
|
|
40
35
|
//#endregion
|
|
41
|
-
export { GameVariantMismatchError, loadGame
|
|
36
|
+
export { GameVariantMismatchError, loadGame };
|
|
42
37
|
|
|
43
38
|
//# sourceMappingURL=GameUtil.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GameUtil.mjs","names":[],"sources":["../../src/util/load-game.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { Database, Obj, type Ref, Type } from '@dxos/echo';\n\nimport { Game } from '#types';\n\nexport class GameVariantMismatchError extends Error {\n readonly _tag = 'GameVariantMismatchError';\n constructor(\n readonly gameId: string,\n readonly expectedTypename: string,\n readonly actualTypename: string,\n ) {\n super(`Game ${gameId} variant typename mismatch: expected ${expectedTypename}, got ${actualTypename}.`);\n }\n}\n\n/**\n * Loads a Game from a Ref and resolves its variant state, asserting the variant matches\n * the expected type. Returns the typed pair.\n *\n * Use in operation handlers that declare `input.game: GameRef<MyVariantState>`.\n */\nexport const loadGame = <S extends Type.AnyObj>(\n ref: Ref.Ref<Game.Game>,\n variantType: S,\n): Effect.Effect<{ game: Game.Game; variant: Type.InstanceType<S> }, Error, Database.Service> =>\n Effect.gen(function* () {\n const game = yield* Database.load(ref);\n const variant = yield* Database.load(game.variant);\n if (!Obj.instanceOf(variantType, variant)) {\n const expected = Type.getTypename(variantType) ?? 'unknown';\n const actual = Obj.getTypename(variant as Obj.Any) ?? 'unknown';\n return yield* Effect.fail(new GameVariantMismatchError(game.id, expected, actual));\n }\n return { game, variant: variant as Type.InstanceType<S> };\n });\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"GameUtil.mjs","names":[],"sources":["../../src/util/load-game.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport { Database, Obj, type Ref, Type } from '@dxos/echo';\n\nimport { Game } from '#types';\n\nexport class GameVariantMismatchError extends Error {\n readonly _tag = 'GameVariantMismatchError';\n constructor(\n readonly gameId: string,\n readonly expectedTypename: string,\n readonly actualTypename: string,\n ) {\n super(`Game ${gameId} variant typename mismatch: expected ${expectedTypename}, got ${actualTypename}.`);\n }\n}\n\n/**\n * Loads a Game from a Ref and resolves its variant state, asserting the variant matches\n * the expected type. Returns the typed pair.\n *\n * Use in operation handlers that declare `input.game: GameRef<MyVariantState>`.\n */\nexport const loadGame = <S extends Type.AnyObj>(\n ref: Ref.Ref<Game.Game>,\n variantType: S,\n): Effect.Effect<{ game: Game.Game; variant: Type.InstanceType<S> }, Error, Database.Service> =>\n Effect.gen(function* () {\n const game = yield* Database.load(ref);\n const variant = yield* Database.load(game.variant);\n if (!Obj.instanceOf(variantType, variant)) {\n const expected = Type.getTypename(variantType) ?? 'unknown';\n const actual = Obj.getTypename(variant as Obj.Any) ?? 'unknown';\n return yield* Effect.fail(new GameVariantMismatchError(game.id, expected, actual));\n }\n return { game, variant: variant as Type.InstanceType<S> };\n });\n"],"mappings":";;;AAUA,IAAa,2BAAb,cAA8C,MAAM;CAGvC;CACA;CACA;CAJX,OAAgB;CAChB,YACE,QACA,kBACA,gBACA;EACA,MAAM,QAAQ,OAAO,uCAAuC,iBAAiB,QAAQ,eAAe,EAAE;EAJ7F,KAAA,SAAA;EACA,KAAA,mBAAA;EACA,KAAA,iBAAA;CAGX;AACF;;;;;;;AAQA,IAAa,YACX,KACA,gBAEA,OAAO,IAAI,aAAa;CACtB,MAAM,OAAO,OAAO,SAAS,KAAK,GAAG;CACrC,MAAM,UAAU,OAAO,SAAS,KAAK,KAAK,OAAO;CACjD,IAAI,CAAC,IAAI,WAAW,aAAa,OAAO,GAAG;EACzC,MAAM,WAAW,KAAK,YAAY,WAAW,KAAK;EAClD,MAAM,SAAS,IAAI,YAAY,OAAkB,KAAK;EACtD,OAAO,OAAO,OAAO,KAAK,IAAI,yBAAyB,KAAK,IAAI,UAAU,MAAM,CAAC;CACnF;CACA,OAAO;EAAE;EAAe;CAAgC;AAC1D,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { t as PLUGIN_default } from "./chunk-PLUGIN.mjs";
|
|
2
|
+
import * as AppCapability from "@dxos/app-toolkit/AppCapability";
|
|
3
|
+
import { translations } from "#translations";
|
|
4
|
+
import * as SpaceCapability from "@dxos/plugin-space/SpaceCapability";
|
|
5
|
+
import { meta } from "#meta";
|
|
6
|
+
//#region src/capabilities/gen/browser.ts
|
|
7
|
+
var CreateObject = SpaceCapability.createObject(() => import("./chunk-create-object.mjs"), { environments: ["browser", "tauri"] });
|
|
8
|
+
var ReactSurface = AppCapability.surface(() => import("./chunk-react-surface.mjs"), { roles: [
|
|
9
|
+
"org.dxos.role.article",
|
|
10
|
+
"org.dxos.role.cardContent",
|
|
11
|
+
"org.dxos.role.section"
|
|
12
|
+
] });
|
|
13
|
+
var Schema = AppCapability.schema(() => import("./chunk-schema.mjs"));
|
|
14
|
+
var PluginAsset = AppCapability.pluginAsset({
|
|
15
|
+
pluginId: meta.profile.key,
|
|
16
|
+
path: "PLUGIN.mdl",
|
|
17
|
+
content: PLUGIN_default,
|
|
18
|
+
mimeType: "application/x-mdl"
|
|
19
|
+
});
|
|
20
|
+
var Translations = AppCapability.translations(translations, { environments: [
|
|
21
|
+
"browser",
|
|
22
|
+
"node",
|
|
23
|
+
"tauri"
|
|
24
|
+
] });
|
|
25
|
+
//#endregion
|
|
26
|
+
export { CreateObject, PluginAsset, ReactSurface, Schema, Translations };
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=capabilities.browser.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities.browser.mjs","names":[],"sources":["../../src/capabilities/gen/browser.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// GENERATED by dx-plugin — do not edit. Sliced from ../index.ts for the 'browser' condition.\n\nimport * as AppCapability from '@dxos/app-toolkit/AppCapability';\nimport * as SpaceCapability from '@dxos/plugin-space/SpaceCapability';\n\nimport { meta } from '#meta';\nimport { translations } from '#translations';\n\nimport pluginSpec from '../../../PLUGIN.mdl?raw';\n\n// Browser-only: the entry supplies `CreateGamePanel`, the React form that picks the variant and\n// collects its input.\nexport const CreateObject = SpaceCapability.createObject(() => import('../create-object.ts'), {\n environments: ['browser', 'tauri'],\n});\n\nexport const ReactSurface = AppCapability.surface(() => import('../react-surface.ts'), {\n roles: ['org.dxos.role.article', 'org.dxos.role.cardContent', 'org.dxos.role.section'],\n});\n\nexport const Schema = AppCapability.schema(() => import('../schema.ts'));\n\nexport const PluginAsset = AppCapability.pluginAsset({\n pluginId: meta.profile.key,\n path: 'PLUGIN.mdl',\n content: pluginSpec,\n mimeType: 'application/x-mdl',\n});\n\nexport const Translations = AppCapability.translations(translations, {\n environments: ['browser', 'node', 'tauri'],\n});\n"],"mappings":";;;;;;AAgBA,IAAa,eAAe,gBAAgB,mBAAmB,OAAO,8BAAwB,EAC5F,cAAc,CAAC,WAAW,OAAO,EACnC,CAAC;AAED,IAAa,eAAe,cAAc,cAAc,OAAO,8BAAwB,EACrF,OAAO;CAAC;CAAyB;CAA6B;AAAuB,EACvF,CAAC;AAED,IAAa,SAAS,cAAc,aAAa,OAAO,qBAAe;AAEvE,IAAa,cAAc,cAAc,YAAY;CACnD,UAAU,KAAK,QAAQ;CACvB,MAAM;CACN,SAAS;CACT,UAAU;AACZ,CAAC;AAED,IAAa,eAAe,cAAc,aAAa,cAAc,EACnE,cAAc;CAAC;CAAW;CAAQ;AAAO,EAC3C,CAAC"}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
+
import { t as PLUGIN_default } from "./chunk-PLUGIN.mjs";
|
|
1
2
|
import * as AppCapability from "@dxos/app-toolkit/AppCapability";
|
|
2
3
|
import { translations } from "#translations";
|
|
3
4
|
import * as SpaceCapability from "@dxos/plugin-space/SpaceCapability";
|
|
4
5
|
import { meta } from "#meta";
|
|
5
|
-
//#region PLUGIN.mdl?raw
|
|
6
|
-
var PLUGIN_default = "---\nid: org.dxos.plugin.game\nname: GamePlugin\nversion: 0.1.0\n---\n\nA generic game host plugin for `DXOS` Composer that provides shared infrastructure for\nmulti-player, local-first turn-based games. Variant plugins (e.g. plugin-chess,\nplugin-tictactoe) contribute a `GameVariant` capability and game-specific surfaces;\nplugin-game supplies the `Game` ECHO type, create flow, and dispatch scaffolding.\n\n## Extensions\n\nThe following extension dialects are used in this document.\nEach extension is defined in the Appendix or resolved via its URI.\n\n| Term | URI |\n|-------------|--------------------------------|\n| `type` | `org.dxos.mdl.type@1.0` |\n| `feat` | `org.dxos.mdl.feat@1.0` |\n| `test` | `org.dxos.mdl.test@1.0` |\n| `component` | `org.dxos.mdl.component@1.0` |\n| `op` | `org.dxos.mdl.op@1.0` |\n\n## Types\n\n```mdl\ntype Player\n fields:\n role: string # variant-defined (e.g. \"white\", \"x\"); required\n identity?: string # DID of a human player; absent for AI / empty seats\n name?: string # display label for AI or guest players\n```\n\n```mdl\ntype Game\n fields:\n name?: string\n players?: Player[] # populated during or after creation\n variant: Ref<Unknown> # reference to variant-specific state object (e.g. Chess.State)\n typename: org.dxos.type.game\n version: 0.1.0\n```\n\n```mdl\ntype GameVariant\n desc: |\n Contribution interface from a variant plugin. Registered via\n `Capability.contributes(GameCapabilities.VariantProvider, variant)`.\n fields:\n id: string # stable variant id; matches the variant state typename\n label: string # human-readable name shown in the picker\n icon?: string # Phosphor icon name\n variantSchema: Schema # Effect/Schema for the ECHO state object\n inputSchema?: Schema # optional form schema shown after variant is selected\n roles: string[] # player roles (e.g. [\"white\", \"black\"])\n createVariant: Effect # factory that builds the variant ECHO state object\n card?: ComponentType # optional Card surface component\n article?: ComponentType # optional Article/Section surface component\n```\n\n## Components\n\n```mdl\ncomponent CreateGamePanel\n desc: |\n Two-stage creation panel. Stage 1 shows a searchable variant picker.\n Stage 2 (optional) shows a form rendered from the variant's inputSchema.\n props:\n target: Space | Obj\n onCreateObject: (args: { variantId: string; input?: Record }) => void\n variants?: GameVariant[] # override; defaults to GameCapabilities.VariantProvider\n state:\n selectedId?: string # id of the variant chosen in stage 1\n actions:\n selectVariant(id: string)\n submitForm(input: Record)\n layout: |\n ┌─────────────────────────┐\n │ Stage 1: Variant Picker │\n │ ┌───────────────────┐ │\n │ │ Search input │ │\n │ ├───────────────────┤ │\n │ │ variant row │ │\n │ │ variant row │ │\n │ └───────────────────┘ │\n ├─────────────────────────┤\n │ Stage 2: Input Form │\n │ (rendered from schema) │\n │ [Submit] │\n └─────────────────────────┘\n```\n\n```mdl\ncomponent GameArticle\n desc: |\n Article / Section surface for Game objects. Resolves the variant via\n `game.variant` ref, finds the matching `GameVariant` by typename, and\n delegates rendering to `variant.article`.\n props:\n game: Game\n role?: string\n attendableId?: string\n state:\n variant: Unknown # live variant state object from the Ref\n match?: GameVariant # resolved GameVariant from capabilities\n layout: |\n ┌─────────────────────────┐\n │ [variant.article] │\n │ (fallback: unsupported │\n │ variant message) │\n └─────────────────────────┘\n```\n\n```mdl\ncomponent GameCard\n desc: |\n Card surface for Game objects. Follows the same variant-resolution\n logic as GameArticle and delegates to `variant.card`.\n props:\n game: Game\n role?: string\n state:\n variant: Unknown\n match?: GameVariant\n```\n\n## Operations\n\n```mdl\nop createGame\n desc: |\n Creates a variant state object via `variant.createVariant`, adds it\n to the space as a hidden object, then wraps it in a top-level Game.\n If the second write fails the variant state object is rolled back.\n input:\n variantId: string\n input?: Record # variant-specific form values\n target: Space | Obj\n targetNodeId?: string\n output: Game\n errors:\n UnknownVariant: no registered GameVariant matches variantId\n effects: [echo:write]\n requires: [GameCapabilities.VariantProvider, SpaceOperation.AddObject, SpaceOperation.RemoveObjects]\n note: |\n Invoked by CreateObjectEntry.createObject.\n Variant state is added with `hidden: true` so it does not appear\n as a top-level item in the user's space graph.\n```\n\n## Features\n\n```mdl\nfeat F-1: Variant Registration\n\n req F-1.1:\n when: a variant plugin loads\n then: it contributes a GameVariant via `Capability.contributes(GameCapabilities.VariantProvider, …)`\n\n req F-1.2:\n when: GameCapabilities.VariantProvider contributions are queried\n then: all registered variants are returned as an ordered list\n```\n\n```mdl\nfeat F-2: Game Creation\n\n req F-2.1:\n when: user opens the create-object flow for type `org.dxos.type.game`\n then: CreateGamePanel is shown with a searchable list of available variants\n\n req F-2.2:\n when: user selects a variant with no inputSchema\n then: op:createGame is invoked immediately with empty input\n\n req F-2.3:\n when: user selects a variant that has an inputSchema\n then: a form rendered from the schema is shown; op:createGame is invoked on submit\n\n req F-2.4:\n when: op:createGame completes\n then: a Game object appears in the space graph; variant state is hidden\n```\n\n```mdl\nfeat F-3: Game Article / Card Rendering\n\n req F-3.1:\n when: Composer navigates to a Game object\n then: GameArticle resolves `game.variant` and delegates to the matching `variant.article`\n\n req F-3.2:\n when: a Game object is shown in a Card layout\n then: GameCard resolves `game.variant` and delegates to `variant.card`\n\n req F-3.3:\n when: no matching variant is found for the loaded state object\n then: GameArticle renders an \"Unsupported game variant\" fallback message\n\n req F-3.4:\n when: the variant ref is not yet loaded\n then: neither GameArticle nor GameCard renders (returns null)\n```\n\n## Acceptance\n\n```mdl\ntest T-1: Variant picker shows all contributions\n given: two variant plugins have contributed GameVariants\n when: the user opens the create-game panel\n then:\n - CreateGamePanel lists both variants\n - variants are sorted alphabetically\n```\n\n```mdl\ntest T-2: Create game without form\n given: a variant with no inputSchema is selected\n when: user clicks the variant item\n then:\n - onCreateObject is called immediately with { variantId }\n - no form is shown\n```\n\n```mdl\ntest T-3: Create game with form\n given: a variant with an inputSchema is selected\n when: user fills and submits the form\n then:\n - onCreateObject is called with { variantId, input: <form values> }\n```\n\n```mdl\ntest T-4: Atomic game creation\n given: a valid variantId and target space\n when: op:createGame is invoked\n then:\n - variant state object added to space (hidden)\n - Game object added with game.variant pointing to the state object\n - both writes are visible to other peers via ECHO replication\n```\n\n```mdl\ntest T-5: Rollback on second write failure\n given: variant state is successfully written\n when: the second write (Game object) fails\n then:\n - RemoveObjects is invoked for the variant state\n - no orphaned state object remains in the space\n```\n\n```mdl\ntest T-6: Unknown variant rejected\n given: op:createGame is invoked with an unregistered variantId\n when: the handler runs\n then: UnknownVariant error is returned; no ECHO writes occur\n```\n\n```mdl\ntest T-7: Unsupported variant fallback\n given: a Game whose variant typename has no registered GameVariant\n when: GameArticle renders\n then: \"Unsupported game variant\" message is shown instead of crashing\n```\n\n---\n\n## Appendix: Extension Definitions\n\nExtension block types used in this document are defined below using\nthe core `ext` primitive — the only construct the base language provides.\n\n```mdl\next type\n uri: org.dxos.mdl.type@1.0\n desc: A named data structure with typed fields and optional literals.\n fields:\n desc?: Prose\n fields?: FieldMap # name[?]: TypeExpr (# inline comment)\n literals?: UnionList # a | b | c\n extends?: TypeRef[]\n```\n\n```mdl\next feat\n uri: org.dxos.mdl.feat@1.0\n desc: A named feature grouping one or more requirements.\n fields:\n desc?: Prose\n req: RequirementList\n nesting: self # feat blocks may contain feat blocks\n```\n\n```mdl\next test\n uri: org.dxos.mdl.test@1.0\n desc: An acceptance scenario expressed as given / when / then steps.\n fields:\n given?: Step | Step[]\n when?: Step | Step[]\n then: Step | Step[]\n tags?: TagList\n```\n\n```mdl\next component\n uri: org.dxos.mdl.component@1.0\n desc: A UI component with props, internal state, slots, actions, and events.\n fields:\n desc?: Prose\n props?: FieldMap # external inputs (immutable inside component)\n state?: FieldMap # internal reactive state\n slots?: FieldMap # named ReactNode injection points\n actions?: ActionMap # methods the component exposes or handles\n emits?: EventMap # events the component raises to its parent\n layout?: CodeBlock # ASCII sketch of visual structure (non-normative)\n```\n\n```mdl\next op\n uri: org.dxos.mdl.op@1.0\n desc: |\n A named operation with typed inputs, outputs, and declared errors.\n Pure ops have no effects or requires. Effectful ops declare both.\n fields:\n desc?: Prose\n input?: FieldMap # named input parameters\n output?: TypeExpr # return type\n errors?: ErrorMap # name: Prose (when this error occurs)\n effects?: EffectList # echo:read | echo:write | http | fs | ...\n requires?: ServiceList # injected service dependencies\n note?: Prose # implementation guidance (non-normative)\n```\n";
|
|
7
|
-
//#endregion
|
|
8
6
|
//#region src/capabilities/index.ts
|
|
9
|
-
var CreateObject = SpaceCapability.createObject(() => import("./chunk-create-object.mjs"), { environments: [] });
|
|
7
|
+
var CreateObject = SpaceCapability.createObject(() => import("./chunk-create-object.mjs"), { environments: ["browser", "tauri"] });
|
|
10
8
|
var ReactSurface = AppCapability.surface(() => import("./chunk-react-surface.mjs"), { roles: [
|
|
11
9
|
"org.dxos.role.article",
|
|
12
10
|
"org.dxos.role.cardContent",
|
|
@@ -19,7 +17,11 @@ var PluginAsset = AppCapability.pluginAsset({
|
|
|
19
17
|
content: PLUGIN_default,
|
|
20
18
|
mimeType: "application/x-mdl"
|
|
21
19
|
});
|
|
22
|
-
var Translations = AppCapability.translations(translations, { environments: [
|
|
20
|
+
var Translations = AppCapability.translations(translations, { environments: [
|
|
21
|
+
"browser",
|
|
22
|
+
"node",
|
|
23
|
+
"tauri"
|
|
24
|
+
] });
|
|
23
25
|
//#endregion
|
|
24
26
|
export { CreateObject, PluginAsset, ReactSurface, Schema, Translations };
|
|
25
27
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.mjs","names":[],"sources":["../../src/capabilities/index.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as AppCapability from '@dxos/app-toolkit/AppCapability';\nimport * as SpaceCapability from '@dxos/plugin-space/SpaceCapability';\n\nimport { meta } from '#meta';\nimport { translations } from '#translations';\n\n// eslint-disable-next-line import/no-relative-packages\nimport pluginSpec from '../../PLUGIN.mdl?raw';\n\n// Browser-only: the entry supplies `CreateGamePanel`, the React form that picks the variant and\n// collects its input.\nexport const CreateObject = SpaceCapability.createObject(() => import('./create-object'), {\n environments: [],\n});\nexport const ReactSurface = AppCapability.surface(() => import('./react-surface'), {\n roles: ['org.dxos.role.article', 'org.dxos.role.cardContent', 'org.dxos.role.section'],\n});\nexport const Schema = AppCapability.schema(() => import('./schema'));\nexport const PluginAsset = AppCapability.pluginAsset({\n pluginId: meta.profile.key,\n path: 'PLUGIN.mdl',\n content: pluginSpec,\n mimeType: 'application/x-mdl',\n});\nexport const Translations = AppCapability.translations(translations, {\n environments: ['node'],\n});\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"capabilities.mjs","names":[],"sources":["../../src/capabilities/index.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as AppCapability from '@dxos/app-toolkit/AppCapability';\nimport * as SpaceCapability from '@dxos/plugin-space/SpaceCapability';\n\nimport { meta } from '#meta';\nimport { translations } from '#translations';\n\n// eslint-disable-next-line import/no-relative-packages\nimport pluginSpec from '../../PLUGIN.mdl?raw';\n\n// Browser-only: the entry supplies `CreateGamePanel`, the React form that picks the variant and\n// collects its input.\nexport const CreateObject = SpaceCapability.createObject(() => import('./create-object.ts'), {\n environments: ['browser', 'tauri'],\n});\nexport const ReactSurface = AppCapability.surface(() => import('./react-surface.ts'), {\n roles: ['org.dxos.role.article', 'org.dxos.role.cardContent', 'org.dxos.role.section'],\n});\nexport const Schema = AppCapability.schema(() => import('./schema.ts'));\nexport const PluginAsset = AppCapability.pluginAsset({\n pluginId: meta.profile.key,\n path: 'PLUGIN.mdl',\n content: pluginSpec,\n mimeType: 'application/x-mdl',\n});\nexport const Translations = AppCapability.translations(translations, {\n environments: ['browser', 'node', 'tauri'],\n});\n"],"mappings":";;;;;;AAeA,IAAa,eAAe,gBAAgB,mBAAmB,OAAO,8BAAuB,EAC3F,cAAc,CAAC,WAAW,OAAO,EACnC,CAAC;AACD,IAAa,eAAe,cAAc,cAAc,OAAO,8BAAuB,EACpF,OAAO;CAAC;CAAyB;CAA6B;AAAuB,EACvF,CAAC;AACD,IAAa,SAAS,cAAc,aAAa,OAAO,qBAAc;AACtE,IAAa,cAAc,cAAc,YAAY;CACnD,UAAU,KAAK,QAAQ;CACvB,MAAM;CACN,SAAS;CACT,UAAU;AACZ,CAAC;AACD,IAAa,eAAe,cAAc,aAAa,cAAc,EACnE,cAAc;CAAC;CAAW;CAAQ;AAAO,EAC3C,CAAC"}
|
|
@@ -2,7 +2,11 @@ import * as AppCapability from "@dxos/app-toolkit/AppCapability";
|
|
|
2
2
|
import { translations } from "#translations";
|
|
3
3
|
//#region src/capabilities/gen/node.ts
|
|
4
4
|
var Schema = AppCapability.schema(() => import("./chunk-schema.mjs"));
|
|
5
|
-
var Translations = AppCapability.translations(translations, { environments: [
|
|
5
|
+
var Translations = AppCapability.translations(translations, { environments: [
|
|
6
|
+
"browser",
|
|
7
|
+
"node",
|
|
8
|
+
"tauri"
|
|
9
|
+
] });
|
|
6
10
|
var CreateObject = void 0;
|
|
7
11
|
var PluginAsset = void 0;
|
|
8
12
|
var ReactSurface = void 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.node.mjs","names":[],"sources":["../../src/capabilities/gen/node.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// GENERATED by dx-plugin — do not edit. Sliced from ../index.ts for the 'node' condition.\n\nimport * as AppCapability from '@dxos/app-toolkit/AppCapability';\n\nimport { translations } from '#translations';\n\nexport const Schema = AppCapability.schema(() => import('../schema'));\n\nexport const Translations = AppCapability.translations(translations, {\n environments: ['node'],\n});\n\nexport const CreateObject = undefined;\nexport const PluginAsset = undefined;\nexport const ReactSurface = undefined;\n"],"mappings":";;;AAUA,IAAa,SAAS,cAAc,aAAa,OAAO,
|
|
1
|
+
{"version":3,"file":"capabilities.node.mjs","names":[],"sources":["../../src/capabilities/gen/node.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// GENERATED by dx-plugin — do not edit. Sliced from ../index.ts for the 'node' condition.\n\nimport * as AppCapability from '@dxos/app-toolkit/AppCapability';\n\nimport { translations } from '#translations';\n\nexport const Schema = AppCapability.schema(() => import('../schema.ts'));\n\nexport const Translations = AppCapability.translations(translations, {\n environments: ['browser', 'node', 'tauri'],\n});\n\nexport const CreateObject = undefined;\nexport const PluginAsset = undefined;\nexport const ReactSurface = undefined;\n"],"mappings":";;;AAUA,IAAa,SAAS,cAAc,aAAa,OAAO,qBAAe;AAEvE,IAAa,eAAe,cAAc,aAAa,cAAc,EACnE,cAAc;CAAC;CAAW;CAAQ;AAAO,EAC3C,CAAC;AAED,IAAa,eAAe,KAAA;AAC5B,IAAa,cAAc,KAAA;AAC3B,IAAa,eAAe,KAAA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { t as PLUGIN_default } from "./chunk-PLUGIN.mjs";
|
|
2
|
+
import * as AppCapability from "@dxos/app-toolkit/AppCapability";
|
|
3
|
+
import { translations } from "#translations";
|
|
4
|
+
import * as SpaceCapability from "@dxos/plugin-space/SpaceCapability";
|
|
5
|
+
import { meta } from "#meta";
|
|
6
|
+
//#region src/capabilities/gen/tauri.ts
|
|
7
|
+
var CreateObject = SpaceCapability.createObject(() => import("./chunk-create-object.mjs"), { environments: ["browser", "tauri"] });
|
|
8
|
+
var ReactSurface = AppCapability.surface(() => import("./chunk-react-surface.mjs"), { roles: [
|
|
9
|
+
"org.dxos.role.article",
|
|
10
|
+
"org.dxos.role.cardContent",
|
|
11
|
+
"org.dxos.role.section"
|
|
12
|
+
] });
|
|
13
|
+
var Schema = AppCapability.schema(() => import("./chunk-schema.mjs"));
|
|
14
|
+
var PluginAsset = AppCapability.pluginAsset({
|
|
15
|
+
pluginId: meta.profile.key,
|
|
16
|
+
path: "PLUGIN.mdl",
|
|
17
|
+
content: PLUGIN_default,
|
|
18
|
+
mimeType: "application/x-mdl"
|
|
19
|
+
});
|
|
20
|
+
var Translations = AppCapability.translations(translations, { environments: [
|
|
21
|
+
"browser",
|
|
22
|
+
"node",
|
|
23
|
+
"tauri"
|
|
24
|
+
] });
|
|
25
|
+
//#endregion
|
|
26
|
+
export { CreateObject, PluginAsset, ReactSurface, Schema, Translations };
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=capabilities.tauri.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities.tauri.mjs","names":[],"sources":["../../src/capabilities/gen/tauri.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// GENERATED by dx-plugin — do not edit. Sliced from ../index.ts for the 'tauri' condition.\n\nimport * as AppCapability from '@dxos/app-toolkit/AppCapability';\nimport * as SpaceCapability from '@dxos/plugin-space/SpaceCapability';\n\nimport { meta } from '#meta';\nimport { translations } from '#translations';\n\nimport pluginSpec from '../../../PLUGIN.mdl?raw';\n\n// Browser-only: the entry supplies `CreateGamePanel`, the React form that picks the variant and\n// collects its input.\nexport const CreateObject = SpaceCapability.createObject(() => import('../create-object.ts'), {\n environments: ['browser', 'tauri'],\n});\n\nexport const ReactSurface = AppCapability.surface(() => import('../react-surface.ts'), {\n roles: ['org.dxos.role.article', 'org.dxos.role.cardContent', 'org.dxos.role.section'],\n});\n\nexport const Schema = AppCapability.schema(() => import('../schema.ts'));\n\nexport const PluginAsset = AppCapability.pluginAsset({\n pluginId: meta.profile.key,\n path: 'PLUGIN.mdl',\n content: pluginSpec,\n mimeType: 'application/x-mdl',\n});\n\nexport const Translations = AppCapability.translations(translations, {\n environments: ['browser', 'node', 'tauri'],\n});\n"],"mappings":";;;;;;AAgBA,IAAa,eAAe,gBAAgB,mBAAmB,OAAO,8BAAwB,EAC5F,cAAc,CAAC,WAAW,OAAO,EACnC,CAAC;AAED,IAAa,eAAe,cAAc,cAAc,OAAO,8BAAwB,EACrF,OAAO;CAAC;CAAyB;CAA6B;AAAuB,EACvF,CAAC;AAED,IAAa,SAAS,cAAc,aAAa,OAAO,qBAAe;AAEvE,IAAa,cAAc,cAAc,YAAY;CACnD,UAAU,KAAK,QAAQ;CACvB,MAAM;CACN,SAAS;CACT,UAAU;AACZ,CAAC;AAED,IAAa,eAAe,cAAc,aAAa,cAAc,EACnE,cAAc;CAAC;CAAW;CAAQ;AAAO,EAC3C,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capabilities.workerd.mjs","names":[],"sources":["../../src/capabilities/gen/workerd.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// GENERATED by dx-plugin — do not edit. Sliced from ../index.ts for the 'workerd' condition.\n\nimport * as AppCapability from '@dxos/app-toolkit/AppCapability';\n\nexport const Schema = AppCapability.schema(() => import('../schema'));\n\nexport const CreateObject = undefined;\nexport const PluginAsset = undefined;\nexport const ReactSurface = undefined;\nexport const Translations = undefined;\n"],"mappings":";;AAQA,IAAa,SAAS,cAAc,aAAa,OAAO,
|
|
1
|
+
{"version":3,"file":"capabilities.workerd.mjs","names":[],"sources":["../../src/capabilities/gen/workerd.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\n// GENERATED by dx-plugin — do not edit. Sliced from ../index.ts for the 'workerd' condition.\n\nimport * as AppCapability from '@dxos/app-toolkit/AppCapability';\n\nexport const Schema = AppCapability.schema(() => import('../schema.ts'));\n\nexport const CreateObject = undefined;\nexport const PluginAsset = undefined;\nexport const ReactSurface = undefined;\nexport const Translations = undefined;\n"],"mappings":";;AAQA,IAAa,SAAS,cAAc,aAAa,OAAO,qBAAe;AAEvE,IAAa,eAAe,KAAA;AAC5B,IAAa,cAAc,KAAA;AAC3B,IAAa,eAAe,KAAA;AAC5B,IAAa,eAAe,KAAA"}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { meta } from "#meta";
|
|
2
|
+
import { Obj } from "@dxos/echo";
|
|
2
3
|
import { GameCapabilities } from "#types";
|
|
3
4
|
import "react";
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import { useTranslation } from "@dxos/react-ui";
|
|
5
|
+
import * as Hooks from "@dxos/app-framework/Hooks";
|
|
6
|
+
import * as UiHooks from "@dxos/react-ui/Hooks";
|
|
7
7
|
import { jsx } from "react/jsx-runtime";
|
|
8
8
|
import { useResolveRef } from "@dxos/echo-react";
|
|
9
9
|
//#region src/containers/GameArticle/GameArticle.tsx
|
|
10
10
|
var GameArticle = ({ role, attendableId, subject: game }) => {
|
|
11
|
-
const { t } = useTranslation(meta.profile.key);
|
|
12
|
-
const variants = useCapabilities(GameCapabilities.VariantProvider);
|
|
11
|
+
const { t } = UiHooks.useTranslation(meta.profile.key);
|
|
12
|
+
const variants = Hooks.useCapabilities(GameCapabilities.VariantProvider);
|
|
13
13
|
const variant = useResolveRef(game.variant);
|
|
14
14
|
if (!variant) return null;
|
|
15
15
|
const variantTypename = Obj.getTypename(variant);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk-GameArticle.mjs","names":[],"sources":["../../src/containers/GameArticle/GameArticle.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport React from 'react';\n\nimport
|
|
1
|
+
{"version":3,"file":"chunk-GameArticle.mjs","names":[],"sources":["../../src/containers/GameArticle/GameArticle.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport React from 'react';\n\nimport * as Hooks from '@dxos/app-framework/Hooks';\nimport type * as AppSurface from '@dxos/app-toolkit/AppSurface';\nimport { Obj } from '@dxos/echo';\nimport { useResolveRef } from '@dxos/echo-react';\nimport * as UiHooks from '@dxos/react-ui/Hooks';\n\nimport { meta } from '#meta';\nimport { Game, GameCapabilities } from '#types';\n\nexport type GameArticleProps = AppSurface.ObjectArticleProps<Game.Game>;\n\nexport const GameArticle = ({ role, attendableId, subject: game }: GameArticleProps) => {\n const { t } = UiHooks.useTranslation(meta.profile.key);\n const variants = Hooks.useCapabilities(GameCapabilities.VariantProvider);\n // Resolved live rather than as a snapshot: variants mutate their state, and a snapshot is frozen.\n const variant = useResolveRef(game.variant);\n\n if (!variant) {\n return null;\n }\n\n const variantTypename = Obj.getTypename(variant);\n const match = variants.find((v) => v.id === variantTypename);\n if (!match?.article) {\n return (\n <div className='p-4 text-sm'>{t('unsupported-variant.label', { defaultValue: 'Unsupported game variant' })}</div>\n );\n }\n\n const Component = match.article;\n return <Component game={game} variant={variant} role={role} attendableId={attendableId} />;\n};\n\nGameArticle.displayName = 'GameArticle';\n"],"mappings":";;;;;;;;;AAiBA,IAAa,eAAe,EAAE,MAAM,cAAc,SAAS,WAA6B;CACtF,MAAM,EAAE,MAAM,QAAQ,eAAe,KAAK,QAAQ,GAAG;CACrD,MAAM,WAAW,MAAM,gBAAgB,iBAAiB,eAAe;CAEvE,MAAM,UAAU,cAAc,KAAK,OAAO;CAE1C,IAAI,CAAC,SACH,OAAO;CAGT,MAAM,kBAAkB,IAAI,YAAY,OAAO;CAC/C,MAAM,QAAQ,SAAS,MAAM,MAAM,EAAE,OAAO,eAAe;CAC3D,IAAI,CAAC,OAAO,SACV,OACE,oBAAC,OAAD;EAAK,WAAU;EAAe,UAAA,EAAE,6BAA6B,EAAE,cAAc,2BAA2B,CAAC;CAAO,CAAA;CAIpH,MAAM,YAAY,MAAM;CACxB,OAAO,oBAAC,WAAD;EAAiB;EAAe;EAAe;EAAoB;CAAe,CAAA;AAC3F;AAEA,YAAY,cAAc"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import { Obj } from "@dxos/echo";
|
|
1
2
|
import { GameCapabilities } from "#types";
|
|
2
3
|
import "react";
|
|
3
|
-
import
|
|
4
|
-
import { Obj } from "@dxos/echo";
|
|
4
|
+
import * as Hooks from "@dxos/app-framework/Hooks";
|
|
5
5
|
import { jsx } from "react/jsx-runtime";
|
|
6
6
|
import { useResolveRef } from "@dxos/echo-react";
|
|
7
7
|
//#region src/containers/GameCard/GameCard.tsx
|
|
8
8
|
var GameCard = ({ role, subject: game }) => {
|
|
9
|
-
const variants = useCapabilities(GameCapabilities.VariantProvider);
|
|
9
|
+
const variants = Hooks.useCapabilities(GameCapabilities.VariantProvider);
|
|
10
10
|
const variant = useResolveRef(game.variant);
|
|
11
11
|
if (!variant) return null;
|
|
12
12
|
const variantTypename = Obj.getTypename(variant);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk-GameCard.mjs","names":[],"sources":["../../src/containers/GameCard/GameCard.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport React from 'react';\n\nimport
|
|
1
|
+
{"version":3,"file":"chunk-GameCard.mjs","names":[],"sources":["../../src/containers/GameCard/GameCard.tsx"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport React from 'react';\n\nimport * as Hooks from '@dxos/app-framework/Hooks';\nimport type * as AppSurface from '@dxos/app-toolkit/AppSurface';\nimport { Obj } from '@dxos/echo';\nimport { useResolveRef } from '@dxos/echo-react';\n\nimport { Game, GameCapabilities } from '#types';\n\nexport type GameCardProps = AppSurface.ObjectCardProps<Game.Game>;\n\nexport const GameCard = ({ role, subject: game }: GameCardProps) => {\n const variants = Hooks.useCapabilities(GameCapabilities.VariantProvider);\n // Resolved live rather than as a snapshot: variants mutate their state, and a snapshot is frozen.\n const variant = useResolveRef(game.variant);\n\n if (!variant) {\n return null;\n }\n\n const variantTypename = Obj.getTypename(variant);\n const match = variants.find((v) => v.id === variantTypename);\n if (!match?.card) {\n return null;\n }\n\n const Component = match.card;\n return <Component game={game} variant={variant} role={role} />;\n};\n\nGameCard.displayName = 'GameCard';\n"],"mappings":";;;;;;;AAeA,IAAa,YAAY,EAAE,MAAM,SAAS,WAA0B;CAClE,MAAM,WAAW,MAAM,gBAAgB,iBAAiB,eAAe;CAEvE,MAAM,UAAU,cAAc,KAAK,OAAO;CAE1C,IAAI,CAAC,SACH,OAAO;CAGT,MAAM,kBAAkB,IAAI,YAAY,OAAO;CAC/C,MAAM,QAAQ,SAAS,MAAM,MAAM,EAAE,OAAO,eAAe;CAC3D,IAAI,CAAC,OAAO,MACV,OAAO;CAGT,MAAM,YAAY,MAAM;CACxB,OAAO,oBAAC,WAAD;EAAiB;EAAe;EAAe;CAAO,CAAA;AAC/D;AAEA,SAAS,cAAc"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
//#region PLUGIN.mdl?raw
|
|
2
|
+
var PLUGIN_default = "---\nid: org.dxos.plugin.game\nname: GamePlugin\nversion: 0.1.0\n---\n\nA generic game host plugin for `DXOS` Composer that provides shared infrastructure for\nmulti-player, local-first turn-based games. Variant plugins (e.g. plugin-chess,\nplugin-tictactoe) contribute a `GameVariant` capability and game-specific surfaces;\nplugin-game supplies the `Game` ECHO type, create flow, and dispatch scaffolding.\n\n## Extensions\n\nThe following extension dialects are used in this document.\nEach extension is defined in the Appendix or resolved via its URI.\n\n| Term | URI |\n|-------------|--------------------------------|\n| `type` | `org.dxos.mdl.type@1.0` |\n| `feat` | `org.dxos.mdl.feat@1.0` |\n| `scenario` | `org.dxos.mdl.scenario@1.0` |\n| `component` | `org.dxos.mdl.component@1.0` |\n| `op` | `org.dxos.mdl.op@1.0` |\n| `test` | `org.dxos.mdl.test@1.0` |\n\n## Types\n\n```mdl\ntype Player\n fields:\n role: string # variant-defined (e.g. \"white\", \"x\"); required\n identity?: string # DID of a human player; absent for AI / empty seats\n name?: string # display label for AI or guest players\n```\n\n```mdl\ntype Game\n fields:\n name?: string\n players?: Player[] # populated during or after creation\n variant: Ref<Unknown> # reference to variant-specific state object (e.g. Chess.State)\n typename: org.dxos.type.game\n version: 0.1.0\n```\n\n```mdl\ntype GameVariant\n desc: |\n Contribution interface from a variant plugin. Registered via\n `Capability.contributes(GameCapabilities.VariantProvider, variant)`.\n fields:\n id: string # stable variant id; matches the variant state typename\n label: string # human-readable name shown in the picker\n icon?: string # Phosphor icon name\n variantSchema: Schema # Effect/Schema for the ECHO state object\n inputSchema?: Schema # optional form schema shown after variant is selected\n roles: string[] # player roles (e.g. [\"white\", \"black\"])\n createVariant: Effect # factory that builds the variant ECHO state object\n card?: ComponentType # optional Card surface component\n article?: ComponentType # optional Article/Section surface component\n```\n\n## Components\n\n```mdl\ncomponent CreateGamePanel\n desc: |\n Two-stage creation panel. Stage 1 shows a searchable variant picker.\n Stage 2 (optional) shows a form rendered from the variant's inputSchema.\n props:\n target: Space | Obj\n onCreateObject: (args: { variantId: string; input?: Record }) => void\n variants?: GameVariant[] # override; defaults to GameCapabilities.VariantProvider\n state:\n selectedId?: string # id of the variant chosen in stage 1\n actions:\n selectVariant(id: string)\n submitForm(input: Record)\n layout: |\n ┌─────────────────────────┐\n │ Stage 1: Variant Picker │\n │ ┌───────────────────┐ │\n │ │ Search input │ │\n │ ├───────────────────┤ │\n │ │ variant row │ │\n │ │ variant row │ │\n │ └───────────────────┘ │\n ├─────────────────────────┤\n │ Stage 2: Input Form │\n │ (rendered from schema) │\n │ [Submit] │\n └─────────────────────────┘\n```\n\n```mdl\ncomponent GameArticle\n desc: |\n Article / Section surface for Game objects. Resolves the variant via\n `game.variant` ref, finds the matching `GameVariant` by typename, and\n delegates rendering to `variant.article`.\n props:\n game: Game\n role?: string\n attendableId?: string\n state:\n variant: Unknown # live variant state object from the Ref\n match?: GameVariant # resolved GameVariant from capabilities\n layout: |\n ┌─────────────────────────┐\n │ [variant.article] │\n │ (fallback: unsupported │\n │ variant message) │\n └─────────────────────────┘\n```\n\n```mdl\ncomponent GameCard\n desc: |\n Card surface for Game objects. Follows the same variant-resolution\n logic as GameArticle and delegates to `variant.card`.\n props:\n game: Game\n role?: string\n state:\n variant: Unknown\n match?: GameVariant\n```\n\n## Operations\n\n```mdl\nop createGame\n desc: |\n Creates a variant state object via `variant.createVariant`, adds it\n to the space as a hidden object, then wraps it in a top-level Game.\n If the second write fails the variant state object is rolled back.\n input:\n variantId: string\n input?: Record # variant-specific form values\n target: Space | Obj\n targetNodeId?: string\n output: Game\n errors:\n UnknownVariant: no registered GameVariant matches variantId\n effects: [echo:write]\n requires: [GameCapabilities.VariantProvider, SpaceOperation.AddObject, SpaceOperation.RemoveObjects]\n note: |\n Invoked by CreateObjectEntry.createObject.\n Variant state is added with `hidden: true` so it does not appear\n as a top-level item in the user's space graph.\n```\n\n## Features\n\n```mdl\nfeat F-1: Variant Registration\n\n req F-1.1:\n when: a variant plugin loads\n then: it contributes a GameVariant via `Capability.contributes(GameCapabilities.VariantProvider, …)`\n\n req F-1.2:\n when: GameCapabilities.VariantProvider contributions are queried\n then: all registered variants are returned as an ordered list\n```\n\n```mdl\nfeat F-2: Game Creation\n\n req F-2.1:\n when: user opens the create-object flow for type `org.dxos.type.game`\n then: CreateGamePanel is shown with a searchable list of available variants\n\n req F-2.2:\n when: user selects a variant with no inputSchema\n then: op:createGame is invoked immediately with empty input\n\n req F-2.3:\n when: user selects a variant that has an inputSchema\n then: a form rendered from the schema is shown; op:createGame is invoked on submit\n\n req F-2.4:\n when: op:createGame completes\n then: a Game object appears in the space graph; variant state is hidden\n```\n\n```mdl\nfeat F-3: Game Article / Card Rendering\n\n req F-3.1:\n when: Composer navigates to a Game object\n then: GameArticle resolves `game.variant` and delegates to the matching `variant.article`\n\n req F-3.2:\n when: a Game object is shown in a Card layout\n then: GameCard resolves `game.variant` and delegates to `variant.card`\n\n req F-3.3:\n when: no matching variant is found for the loaded state object\n then: GameArticle renders an \"Unsupported game variant\" fallback message\n\n req F-3.4:\n when: the variant ref is not yet loaded\n then: neither GameArticle nor GameCard renders (returns null)\n```\n\n## Acceptance\n\n```mdl\nscenario T-1: Variant picker shows all contributions\n given: two variant plugins have contributed GameVariants\n when: the user opens the create-game panel\n then:\n - CreateGamePanel lists both variants\n - variants are sorted alphabetically\n```\n\n```mdl\nscenario T-2: Create game without form\n given: a variant with no inputSchema is selected\n when: user clicks the variant item\n then:\n - onCreateObject is called immediately with { variantId }\n - no form is shown\n```\n\n```mdl\nscenario T-3: Create game with form\n given: a variant with an inputSchema is selected\n when: user fills and submits the form\n then:\n - onCreateObject is called with { variantId, input: <form values> }\n```\n\n```mdl\nscenario T-4: Atomic game creation\n given: a valid variantId and target space\n when: op:createGame is invoked\n then:\n - variant state object added to space (hidden)\n - Game object added with game.variant pointing to the state object\n - both writes are visible to other peers via ECHO replication\n```\n\n```mdl\nscenario T-5: Rollback on second write failure\n given: variant state is successfully written\n when: the second write (Game object) fails\n then:\n - RemoveObjects is invoked for the variant state\n - no orphaned state object remains in the space\n```\n\n```mdl\nscenario T-6: Unknown variant rejected\n given: op:createGame is invoked with an unregistered variantId\n when: the handler runs\n then: UnknownVariant error is returned; no ECHO writes occur\n```\n\n```mdl\nscenario T-7: Unsupported variant fallback\n given: a Game whose variant typename has no registered GameVariant\n when: GameArticle renders\n then: \"Unsupported game variant\" message is shown instead of crashing\n```\n\n---\n\n## QA\n\nExecutable tests. Each step carries a human instruction (`do`) and, where the step maps to an\ninvocable operation, an agent binding (`invoke`). Report failures by test and step number.\n\n```mdl\ntest QA-1: Create a game through the variant picker and render it\n desc: |\n The generic host round-trip — pick a registered variant, create the Game wrapper plus its\n hidden variant state, and confirm dispatch renders the variant's article. Uses\n plugin-chess's variant since it is the only one shipped and enabled by default; the test\n tests plugin-game's dispatch, not chess's own gameplay (see plugin-chess's QA for that).\n actors: human\n covers: [F-1.2, F-2.1, F-2.4, F-3.1, T-2, T-4]\n status: unverified\n given:\n - an identity exists\n - space: a space is open\n - collection: a collection in that space, to create into\n - plugin-chess (or another GameVariant contributor) is enabled, so the picker lists at least one variant\n - no game named \"QA Game\" exists — ABORT rather than deleting one this test did not create\n - game: (bound during the run, not beforehand) the Game object the \"Select the Chess variant\" step creates — recorded by the tester, since no invoke captures it\n steps:\n - name: Open the create-game panel\n do: In the navtree, click + on a collection and choose Game.\n expect: CreateGamePanel opens showing a searchable list including a \"Chess\" row\n note: |\n NO `invoke` — `createGame` is registered as a `CreateObjectEntry.customPanel`, not an\n `Operation.make` with a runtime key, so there is no key to invoke through the operation\n invoker; opening the panel is itself the create-object gesture (see `code-style`/`operations`\n conventions: only `Operation.make` instances get a DXN, and this factory is not one).\n - name: Select the Chess variant\n do: Click the \"Chess\" row (it has no inputSchema, so no form stage follows).\n expect: a Game object appears under the collection, named after the default, with hidden chess variant state referenced by `game.variant`\n note: |\n NO `invoke` for the same reason as the previous step — this whole creation gesture is\n human-only until `createGame` (or an equivalent) is exposed as an invocable operation.\n Because nothing here is scriptable, there is no captured result to thread forward; the\n tester notes the new Game's id manually for the `after` stage below.\n - name: Open the game and confirm dispatch\n do: Click the Game row in the navtree.\n expect: GameArticle resolves `game.variant` to the chess state object and delegates to chess's own article — a starting chess position renders, not the \"Unsupported game variant\" fallback\n after:\n - name: Delete the game\n do: Right-click the Game row in the navtree and choose Delete.\n invoke: org.dxos.operation.space.removeObjects { objects: [$given.game], target: $given.collection }\n expect: the Game row leaves the navtree, its plank closes if open, and the hidden variant state object is also removed\n```\n\n---\n\n## Appendix: Extension Definitions\n\nExtension block types used in this document are defined below using\nthe core `ext` primitive — the only construct the base language provides.\n\n```mdl\next type\n uri: org.dxos.mdl.type@1.0\n desc: A named data structure with typed fields and optional literals.\n fields:\n desc?: Prose\n fields?: FieldMap # name[?]: TypeExpr (# inline comment)\n literals?: UnionList # a | b | c\n extends?: TypeRef[]\n```\n\n```mdl\next feat\n uri: org.dxos.mdl.feat@1.0\n desc: A named feature grouping one or more requirements.\n fields:\n desc?: Prose\n req: RequirementList\n nesting: self # feat blocks may contain feat blocks\n```\n\n```mdl\next scenario\n uri: org.dxos.mdl.scenario@1.0\n desc: An acceptance scenario expressed as given / when / then steps.\n fields:\n given?: Step | Step[]\n when?: Step | Step[]\n then: Step | Step[]\n tags?: TagList\n```\n\n```mdl\next component\n uri: org.dxos.mdl.component@1.0\n desc: A UI component with props, internal state, slots, actions, and events.\n fields:\n desc?: Prose\n props?: FieldMap # external inputs (immutable inside component)\n state?: FieldMap # internal reactive state\n slots?: FieldMap # named ReactNode injection points\n actions?: ActionMap # methods the component exposes or handles\n emits?: EventMap # events the component raises to its parent\n layout?: CodeBlock # ASCII sketch of visual structure (non-normative)\n```\n\n```mdl\next op\n uri: org.dxos.mdl.op@1.0\n desc: |\n A named operation with typed inputs, outputs, and declared errors.\n Pure ops have no effects or requires. Effectful ops declare both.\n fields:\n desc?: Prose\n input?: FieldMap # named input parameters\n output?: TypeExpr # return type\n errors?: ErrorMap # name: Prose (when this error occurs)\n effects?: EffectList # echo:read | echo:write | http | fs | ...\n requires?: ServiceList # injected service dependencies\n note?: Prose # implementation guidance (non-normative)\n```\n";
|
|
3
|
+
//#endregion
|
|
4
|
+
export { PLUGIN_default as t };
|
|
5
|
+
|
|
6
|
+
//# sourceMappingURL=chunk-PLUGIN.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunk-PLUGIN.mjs","names":[],"sources":[],"mappings":""}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Game, GameCapabilities } from "#types";
|
|
2
|
-
import { Database, Type } from "@dxos/echo";
|
|
3
1
|
import * as Effect from "effect/Effect";
|
|
2
|
+
import { Database, Type } from "@dxos/echo";
|
|
3
|
+
import { Game, GameCapabilities } from "#types";
|
|
4
4
|
import * as Capability from "@dxos/app-framework/Capability";
|
|
5
5
|
import * as Operation from "@dxos/compute/Operation";
|
|
6
6
|
import { invariant } from "@dxos/invariant";
|
|
@@ -34,7 +34,7 @@ var create_object_default = Capability.makeModule(Effect.fnUntraced(function* ()
|
|
|
34
34
|
return yield* Operation.invoke(SpaceOperation.AddObject, {
|
|
35
35
|
object: game,
|
|
36
36
|
target: options.target
|
|
37
|
-
}, { spaceId: options.db.spaceId }).pipe(Effect.tapError(() => Operation.invoke(SpaceOperation.RemoveObjects, { objects: [stateObject] }).pipe(Effect.ignore)));
|
|
37
|
+
}, { spaceId: options.db.spaceId }).pipe(Effect.tapError(() => Operation.invoke(SpaceOperation.RemoveObjects, { objects: [stateObject] }, { spaceId: options.db.spaceId }).pipe(Effect.ignore)));
|
|
38
38
|
})
|
|
39
39
|
});
|
|
40
40
|
}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk-create-object.mjs","names":[],"sources":["../../src/capabilities/create-object.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport * as Capability from '@dxos/app-framework/Capability';\nimport * as Operation from '@dxos/compute/Operation';\nimport { Database, Type } from '@dxos/echo';\nimport { invariant } from '@dxos/invariant';\nimport * as SpaceCapabilities from '@dxos/plugin-space/SpaceCapabilities';\nimport * as SpaceOperation from '@dxos/plugin-space/SpaceOperation';\n\nimport { CreateGamePanel } from '#components';\nimport { Game, GameCapabilities } from '#types';\n\ntype CreateOptions = Parameters<SpaceCapabilities.CreateObjectEntry['createObject']>[1];\n\nexport default Capability.makeModule(\n Effect.fnUntraced(function* () {\n return Capability.contribute(SpaceCapabilities.CreateObjectEntry, {\n id: Type.getTypename(Game.Game),\n customPanel: CreateGamePanel,\n createObject: (\n { variantId, input }: { variantId: string; input?: Record<string, any> },\n options: CreateOptions,\n ) =>\n Effect.gen(function* () {\n const variants = yield* Capability.getAll(GameCapabilities.VariantProvider);\n const variant = variants.find((v) => v.id === variantId);\n invariant(variant, `Unknown game variant: ${variantId}`);\n\n // Build variant state object via the variant's factory.\n const stateObject = yield* variant\n .createVariant(input ?? {})\n .pipe(Effect.provideService(Database.Service, Database.makeService(options.db)));\n\n // Add variant state to the database. Stays hidden — it's referenced by Game and\n // shouldn't appear as a top-level item in the user's space.\n yield* Operation.invoke(\n SpaceOperation.AddObject,\n {\n object: stateObject,\n target: options.target,\n },\n { spaceId: options.db.spaceId },\n );\n\n const game = Game.make({\n name: typeof input?.name === 'string' ? input.name : undefined,\n variant: stateObject,\n });\n\n // Add the user-facing Game wrapper. Not hidden — this is the object the user sees\n // and navigates to. If this second write fails, roll back the variant state so we\n // don't leak an orphaned object into the space.\n return yield* Operation.invoke(\n SpaceOperation.AddObject,\n {\n object: game,\n target: options.target,\n },\n { spaceId: options.db.spaceId },\n ).pipe(\n Effect.tapError(() =>\n Operation.invoke(SpaceOperation.RemoveObjects
|
|
1
|
+
{"version":3,"file":"chunk-create-object.mjs","names":[],"sources":["../../src/capabilities/create-object.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport * as Capability from '@dxos/app-framework/Capability';\nimport * as Operation from '@dxos/compute/Operation';\nimport { Database, Type } from '@dxos/echo';\nimport { invariant } from '@dxos/invariant';\nimport * as SpaceCapabilities from '@dxos/plugin-space/SpaceCapabilities';\nimport * as SpaceOperation from '@dxos/plugin-space/SpaceOperation';\n\nimport { CreateGamePanel } from '#components';\nimport { Game, GameCapabilities } from '#types';\n\ntype CreateOptions = Parameters<SpaceCapabilities.CreateObjectEntry['createObject']>[1];\n\nexport default Capability.makeModule(\n Effect.fnUntraced(function* () {\n return Capability.contribute(SpaceCapabilities.CreateObjectEntry, {\n id: Type.getTypename(Game.Game),\n customPanel: CreateGamePanel,\n createObject: (\n { variantId, input }: { variantId: string; input?: Record<string, any> },\n options: CreateOptions,\n ) =>\n Effect.gen(function* () {\n const variants = yield* Capability.getAll(GameCapabilities.VariantProvider);\n const variant = variants.find((v) => v.id === variantId);\n invariant(variant, `Unknown game variant: ${variantId}`);\n\n // Build variant state object via the variant's factory.\n const stateObject = yield* variant\n .createVariant(input ?? {})\n .pipe(Effect.provideService(Database.Service, Database.makeService(options.db)));\n\n // Add variant state to the database. Stays hidden — it's referenced by Game and\n // shouldn't appear as a top-level item in the user's space.\n yield* Operation.invoke(\n SpaceOperation.AddObject,\n {\n object: stateObject,\n target: options.target,\n },\n { spaceId: options.db.spaceId },\n );\n\n const game = Game.make({\n name: typeof input?.name === 'string' ? input.name : undefined,\n variant: stateObject,\n });\n\n // Add the user-facing Game wrapper. Not hidden — this is the object the user sees\n // and navigates to. If this second write fails, roll back the variant state so we\n // don't leak an orphaned object into the space.\n return yield* Operation.invoke(\n SpaceOperation.AddObject,\n {\n object: game,\n target: options.target,\n },\n { spaceId: options.db.spaceId },\n ).pipe(\n Effect.tapError(() =>\n Operation.invoke(\n SpaceOperation.RemoveObjects,\n { objects: [stateObject] },\n { spaceId: options.db.spaceId },\n ).pipe(Effect.ignore),\n ),\n );\n }),\n });\n }),\n);\n"],"mappings":";;;;;;;;;;;AAkBA,IAAA,wBAAe,WAAW,WACxB,OAAO,WAAW,aAAa;CAC7B,OAAO,WAAW,WAAW,kBAAkB,mBAAmB;EAChE,IAAI,KAAK,YAAY,KAAK,IAAI;EAC9B,aAAa;EACb,eACE,EAAE,WAAW,SACb,YAEA,OAAO,IAAI,aAAa;GAEtB,MAAM,WAAU,OADQ,WAAW,OAAO,iBAAiB,eAAe,EAAA,CACjD,MAAM,MAAM,EAAE,OAAO,SAAS;GACvD,UAAU,SAAS,yBAAyB,aAAU;IAAA,YAAA;IAAA,GAAA;IAAA,GAAA;IAAA,GAAA;IAAA,GAAA,CAAA,WAAA,sCAAA;GAAA,CAAC;GAGvD,MAAM,cAAc,OAAO,QACxB,cAAc,SAAS,CAAC,CAAC,CAAA,CACzB,KAAK,OAAO,eAAe,SAAS,SAAS,SAAS,YAAY,QAAQ,EAAE,CAAC,CAAC;GAIjF,OAAO,UAAU,OACf,eAAe,WACf;IACE,QAAQ;IACR,QAAQ,QAAQ;GAClB,GACA,EAAE,SAAS,QAAQ,GAAG,QAAQ,CAChC;GAEA,MAAM,OAAO,KAAK,KAAK;IACrB,MAAM,OAAO,OAAO,SAAS,WAAW,MAAM,OAAO,KAAA;IACrD,SAAS;GACX,CAAC;GAKD,OAAO,OAAO,UAAU,OACtB,eAAe,WACf;IACE,QAAQ;IACR,QAAQ,QAAQ;GAClB,GACA,EAAE,SAAS,QAAQ,GAAG,QAAQ,CAChC,CAAC,CAAC,KACA,OAAO,eACL,UAAU,OACR,eAAe,eACf,EAAE,SAAS,CAAC,WAAW,EAAE,GACzB,EAAE,SAAS,QAAQ,GAAG,QAAQ,CAChC,CAAC,CAAC,KAAK,OAAO,MAAM,CACtB,CACF;EACF,CAAC;CACL,CAAC;AACH,CAAC,CACH"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { Game } from "#types";
|
|
2
|
-
import { Surface } from "@dxos/app-framework/ui";
|
|
3
1
|
import * as Effect from "effect/Effect";
|
|
2
|
+
import { Game } from "#types";
|
|
4
3
|
import * as Capability from "@dxos/app-framework/Capability";
|
|
5
4
|
import * as Capabilities from "@dxos/app-framework/Capabilities";
|
|
6
|
-
import
|
|
5
|
+
import * as Surface from "@dxos/app-framework/Surface";
|
|
6
|
+
import * as AppSurface from "@dxos/app-toolkit/AppSurface";
|
|
7
7
|
import { GameArticle, GameCard } from "#containers";
|
|
8
8
|
//#region src/capabilities/react-surface.ts
|
|
9
9
|
var react_surface_default = Capability.makeModule(() => Effect.succeed(Capability.contribute(Capabilities.ReactSurface, [Surface.create({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk-react-surface.mjs","names":[],"sources":["../../src/capabilities/react-surface.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport * as Capabilities from '@dxos/app-framework/Capabilities';\nimport * as Capability from '@dxos/app-framework/Capability';\nimport
|
|
1
|
+
{"version":3,"file":"chunk-react-surface.mjs","names":[],"sources":["../../src/capabilities/react-surface.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Effect from 'effect/Effect';\n\nimport * as Capabilities from '@dxos/app-framework/Capabilities';\nimport * as Capability from '@dxos/app-framework/Capability';\nimport * as Surface from '@dxos/app-framework/Surface';\nimport * as AppSurface from '@dxos/app-toolkit/AppSurface';\n\nimport { GameArticle, GameCard } from '#containers';\nimport { Game } from '#types';\n\nexport default Capability.makeModule(() =>\n Effect.succeed(\n Capability.contribute(Capabilities.ReactSurface, [\n Surface.create({\n id: 'game',\n filter: AppSurface.oneOf(\n AppSurface.object(AppSurface.Article, Game.Game),\n AppSurface.object(AppSurface.Section, Game.Game),\n ),\n component: GameArticle,\n props: ({ role, data: { subject, attendableId } }) => ({ role, subject, attendableId }),\n }),\n Surface.create({\n id: 'gameCard',\n filter: AppSurface.object(AppSurface.CardContent, Game.Game),\n component: GameCard,\n props: ({ role, data: { subject } }) => ({ role, subject }),\n }),\n ]),\n ),\n);\n"],"mappings":";;;;;;;;AAcA,IAAA,wBAAe,WAAW,iBACxB,OAAO,QACL,WAAW,WAAW,aAAa,cAAc,CAC/C,QAAQ,OAAO;CACb,IAAI;CACJ,QAAQ,WAAW,MACjB,WAAW,OAAO,WAAW,SAAS,KAAK,IAAI,GAC/C,WAAW,OAAO,WAAW,SAAS,KAAK,IAAI,CACjD;CACA,WAAW;CACX,QAAQ,EAAE,MAAM,MAAM,EAAE,SAAS,sBAAsB;EAAE;EAAM;EAAS;CAAa;AACvF,CAAC,GACD,QAAQ,OAAO;CACb,IAAI;CACJ,QAAQ,WAAW,OAAO,WAAW,aAAa,KAAK,IAAI;CAC3D,WAAW;CACX,QAAQ,EAAE,MAAM,MAAM,EAAE,iBAAiB;EAAE;EAAM;CAAQ;AAC3D,CAAC,CACH,CAAC,CACH,CACF"}
|