@dxos/plugin-game 0.0.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/LICENSE +105 -0
- package/PLUGIN.mdl +334 -0
- package/dist/lib/neutral/GameArticle-AUJ4YDKW.mjs +40 -0
- package/dist/lib/neutral/GameArticle-AUJ4YDKW.mjs.map +7 -0
- package/dist/lib/neutral/GameCard-QBKHIL4Q.mjs +32 -0
- package/dist/lib/neutral/GameCard-QBKHIL4Q.mjs.map +7 -0
- package/dist/lib/neutral/GamePlugin.mjs +38 -0
- package/dist/lib/neutral/GamePlugin.mjs.map +7 -0
- package/dist/lib/neutral/GamePlugin.node.mjs +21 -0
- package/dist/lib/neutral/GamePlugin.node.mjs.map +7 -0
- package/dist/lib/neutral/GamePlugin.workerd.mjs +18 -0
- package/dist/lib/neutral/GamePlugin.workerd.mjs.map +7 -0
- package/dist/lib/neutral/capabilities/index.mjs +11 -0
- package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
- package/dist/lib/neutral/chunk-DSUJWWNN.mjs +98 -0
- package/dist/lib/neutral/chunk-DSUJWWNN.mjs.map +7 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
- package/dist/lib/neutral/chunk-R4V5PVU7.mjs +50 -0
- package/dist/lib/neutral/chunk-R4V5PVU7.mjs.map +7 -0
- package/dist/lib/neutral/components/index.mjs +96 -0
- package/dist/lib/neutral/components/index.mjs.map +7 -0
- package/dist/lib/neutral/containers/index.mjs +11 -0
- package/dist/lib/neutral/containers/index.mjs.map +7 -0
- package/dist/lib/neutral/create-object-UAD2JUWF.mjs +51 -0
- package/dist/lib/neutral/create-object-UAD2JUWF.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +24 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/neutral/meta.mjs +8 -0
- package/dist/lib/neutral/meta.mjs.map +7 -0
- package/dist/lib/neutral/plugin.mjs +12 -0
- package/dist/lib/neutral/plugin.mjs.map +7 -0
- package/dist/lib/neutral/react-surface-GO3SKEMA.mjs +35 -0
- package/dist/lib/neutral/react-surface-GO3SKEMA.mjs.map +7 -0
- package/dist/lib/neutral/translations.mjs +34 -0
- package/dist/lib/neutral/translations.mjs.map +7 -0
- package/dist/lib/neutral/types/index.mjs +20 -0
- package/dist/lib/neutral/types/index.mjs.map +7 -0
- package/dist/types/dx.config.d.ts +28 -0
- package/dist/types/dx.config.d.ts.map +1 -0
- package/dist/types/src/GamePlugin.d.ts +4 -0
- package/dist/types/src/GamePlugin.d.ts.map +1 -0
- package/dist/types/src/GamePlugin.node.d.ts +10 -0
- package/dist/types/src/GamePlugin.node.d.ts.map +1 -0
- package/dist/types/src/GamePlugin.workerd.d.ts +4 -0
- package/dist/types/src/GamePlugin.workerd.d.ts.map +1 -0
- package/dist/types/src/capabilities/create-object.d.ts +5 -0
- package/dist/types/src/capabilities/create-object.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +4 -0
- package/dist/types/src/capabilities/index.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface.d.ts +5 -0
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
- package/dist/types/src/components/CreateGamePanel.d.ts +19 -0
- package/dist/types/src/components/CreateGamePanel.d.ts.map +1 -0
- package/dist/types/src/components/CreateGamePanel.stories.d.ts +49 -0
- package/dist/types/src/components/CreateGamePanel.stories.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +2 -0
- package/dist/types/src/components/index.d.ts.map +1 -0
- package/dist/types/src/containers/GameArticle/GameArticle.d.ts +6 -0
- package/dist/types/src/containers/GameArticle/GameArticle.d.ts.map +1 -0
- package/dist/types/src/containers/GameArticle/index.d.ts +2 -0
- package/dist/types/src/containers/GameArticle/index.d.ts.map +1 -0
- package/dist/types/src/containers/GameCard/GameCard.d.ts +6 -0
- package/dist/types/src/containers/GameCard/GameCard.d.ts.map +1 -0
- package/dist/types/src/containers/GameCard/index.d.ts +2 -0
- package/dist/types/src/containers/GameCard/index.d.ts.map +1 -0
- package/dist/types/src/containers/index.d.ts +4 -0
- package/dist/types/src/containers/index.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +3 -0
- package/dist/types/src/index.d.ts.map +1 -0
- package/dist/types/src/meta.d.ts +33 -0
- package/dist/types/src/meta.d.ts.map +1 -0
- package/dist/types/src/plugin.d.ts +3 -0
- package/dist/types/src/plugin.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +36 -0
- package/dist/types/src/translations.d.ts.map +1 -0
- package/dist/types/src/types/Game.d.ts +67 -0
- package/dist/types/src/types/Game.d.ts.map +1 -0
- package/dist/types/src/types/GameCapabilities.d.ts +10 -0
- package/dist/types/src/types/GameCapabilities.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +5 -0
- package/dist/types/src/types/index.d.ts.map +1 -0
- package/dist/types/src/types/types.d.ts +46 -0
- package/dist/types/src/types/types.d.ts.map +1 -0
- package/dist/types/src/util/index.d.ts +2 -0
- package/dist/types/src/util/index.d.ts.map +1 -0
- package/dist/types/src/util/load-game.d.ts +21 -0
- package/dist/types/src/util/load-game.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -0
- package/dx.config.ts +40 -0
- package/package.json +124 -0
- package/src/GamePlugin.node.tsx +24 -0
- package/src/GamePlugin.tsx +27 -0
- package/src/GamePlugin.workerd.tsx +13 -0
- package/src/capabilities/create-object.ts +67 -0
- package/src/capabilities/index.ts +8 -0
- package/src/capabilities/react-surface.tsx +36 -0
- package/src/components/CreateGamePanel.stories.tsx +92 -0
- package/src/components/CreateGamePanel.tsx +127 -0
- package/src/components/index.ts +5 -0
- package/src/containers/GameArticle/GameArticle.tsx +42 -0
- package/src/containers/GameArticle/index.ts +5 -0
- package/src/containers/GameCard/GameCard.tsx +35 -0
- package/src/containers/GameCard/index.ts +5 -0
- package/src/containers/index.ts +8 -0
- package/src/index.ts +6 -0
- package/src/meta.ts +9 -0
- package/src/plugin.ts +9 -0
- package/src/translations.ts +34 -0
- package/src/types/Game.ts +106 -0
- package/src/types/GameCapabilities.ts +19 -0
- package/src/types/index.ts +9 -0
- package/src/types/types.ts +53 -0
- package/src/util/index.ts +5 -0
- package/src/util/load-game.ts +41 -0
- package/src/vite-env.d.ts +10 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Functional Source License, Version 1.1, ALv2 Future License
|
|
2
|
+
|
|
3
|
+
## Abbreviation
|
|
4
|
+
|
|
5
|
+
FSL-1.1-Apache-2.0
|
|
6
|
+
|
|
7
|
+
## Notice
|
|
8
|
+
|
|
9
|
+
Copyright 2026 DXOS
|
|
10
|
+
|
|
11
|
+
## Terms and Conditions
|
|
12
|
+
|
|
13
|
+
### Licensor ("We")
|
|
14
|
+
|
|
15
|
+
The party offering the Software under these Terms and Conditions.
|
|
16
|
+
|
|
17
|
+
### The Software
|
|
18
|
+
|
|
19
|
+
The "Software" is each version of the software that we make available under
|
|
20
|
+
these Terms and Conditions, as indicated by our inclusion of these Terms and
|
|
21
|
+
Conditions with the Software.
|
|
22
|
+
|
|
23
|
+
### License Grant
|
|
24
|
+
|
|
25
|
+
Subject to your compliance with this License Grant and the Patents,
|
|
26
|
+
Redistribution and Trademark clauses below, we hereby grant you the right to
|
|
27
|
+
use, copy, modify, create derivative works, publicly perform, publicly display
|
|
28
|
+
and redistribute the Software for any Permitted Purpose identified below.
|
|
29
|
+
|
|
30
|
+
### Permitted Purpose
|
|
31
|
+
|
|
32
|
+
A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
|
|
33
|
+
means making the Software available to others in a commercial product or
|
|
34
|
+
service that:
|
|
35
|
+
|
|
36
|
+
1. substitutes for the Software;
|
|
37
|
+
|
|
38
|
+
2. substitutes for any other product or service we offer using the Software
|
|
39
|
+
that exists as of the date we make the Software available; or
|
|
40
|
+
|
|
41
|
+
3. offers the same or substantially similar functionality as the Software.
|
|
42
|
+
|
|
43
|
+
Permitted Purposes specifically include using the Software:
|
|
44
|
+
|
|
45
|
+
1. for your internal use and access;
|
|
46
|
+
|
|
47
|
+
2. for non-commercial education;
|
|
48
|
+
|
|
49
|
+
3. for non-commercial research; and
|
|
50
|
+
|
|
51
|
+
4. in connection with professional services that you provide to a licensee
|
|
52
|
+
using the Software in accordance with these Terms and Conditions.
|
|
53
|
+
|
|
54
|
+
### Patents
|
|
55
|
+
|
|
56
|
+
To the extent your use for a Permitted Purpose would necessarily infringe our
|
|
57
|
+
patents, the license grant above includes a license under our patents. If you
|
|
58
|
+
make a claim against any party that the Software infringes or contributes to
|
|
59
|
+
the infringement of any patent, then your patent license to the Software ends
|
|
60
|
+
immediately.
|
|
61
|
+
|
|
62
|
+
### Redistribution
|
|
63
|
+
|
|
64
|
+
The Terms and Conditions apply to all copies, modifications and derivatives of
|
|
65
|
+
the Software.
|
|
66
|
+
|
|
67
|
+
If you redistribute any copies, modifications or derivatives of the Software,
|
|
68
|
+
you must include a copy of or a link to these Terms and Conditions and not
|
|
69
|
+
remove any copyright notices provided in or with the Software.
|
|
70
|
+
|
|
71
|
+
### Disclaimer
|
|
72
|
+
|
|
73
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
|
|
74
|
+
IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
|
|
75
|
+
PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
|
|
76
|
+
|
|
77
|
+
IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
|
|
78
|
+
SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
|
|
79
|
+
EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
|
|
80
|
+
|
|
81
|
+
### Trademarks
|
|
82
|
+
|
|
83
|
+
Except for displaying the License Details and identifying us as the origin of
|
|
84
|
+
the Software, you have no right under these Terms and Conditions to use our
|
|
85
|
+
trademarks, trade names, service marks or product names.
|
|
86
|
+
|
|
87
|
+
## Grant of Future License
|
|
88
|
+
|
|
89
|
+
We hereby irrevocably grant you an additional license to use the Software under
|
|
90
|
+
the Apache License, Version 2.0 that is effective on the second anniversary of
|
|
91
|
+
the date we make the Software available. On or after that date, you may use the
|
|
92
|
+
Software under the Apache License, Version 2.0, in which case the following
|
|
93
|
+
will apply:
|
|
94
|
+
|
|
95
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
96
|
+
this file except in compliance with the License.
|
|
97
|
+
|
|
98
|
+
You may obtain a copy of the License at
|
|
99
|
+
|
|
100
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
101
|
+
|
|
102
|
+
Unless required by applicable law or agreed to in writing, software distributed
|
|
103
|
+
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
104
|
+
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
105
|
+
specific language governing permissions and limitations under the License.
|
package/PLUGIN.mdl
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: org.dxos.plugin.game
|
|
3
|
+
name: GamePlugin
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
A generic game host plugin for `DXOS` Composer that provides shared infrastructure for
|
|
8
|
+
multi-player, local-first turn-based games. Variant plugins (e.g. plugin-chess,
|
|
9
|
+
plugin-tictactoe) contribute a `GameVariant` capability and game-specific surfaces;
|
|
10
|
+
plugin-game supplies the `Game` ECHO type, create flow, and dispatch scaffolding.
|
|
11
|
+
|
|
12
|
+
## Extensions
|
|
13
|
+
|
|
14
|
+
The following extension dialects are used in this document.
|
|
15
|
+
Each extension is defined in the Appendix or resolved via its URI.
|
|
16
|
+
|
|
17
|
+
| Term | URI |
|
|
18
|
+
|-------------|--------------------------------|
|
|
19
|
+
| `type` | `org.dxos.mdl.type@1.0` |
|
|
20
|
+
| `feat` | `org.dxos.mdl.feat@1.0` |
|
|
21
|
+
| `test` | `org.dxos.mdl.test@1.0` |
|
|
22
|
+
| `component` | `org.dxos.mdl.component@1.0` |
|
|
23
|
+
| `op` | `org.dxos.mdl.op@1.0` |
|
|
24
|
+
|
|
25
|
+
## Types
|
|
26
|
+
|
|
27
|
+
```mdl
|
|
28
|
+
type Player
|
|
29
|
+
fields:
|
|
30
|
+
role: string # variant-defined (e.g. "white", "x"); required
|
|
31
|
+
identity?: string # DID of a human player; absent for AI / empty seats
|
|
32
|
+
name?: string # display label for AI or guest players
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```mdl
|
|
36
|
+
type Game
|
|
37
|
+
fields:
|
|
38
|
+
name?: string
|
|
39
|
+
players?: Player[] # populated during or after creation
|
|
40
|
+
variant: Ref<Unknown> # reference to variant-specific state object (e.g. Chess.State)
|
|
41
|
+
typename: org.dxos.type.game
|
|
42
|
+
version: 0.1.0
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```mdl
|
|
46
|
+
type GameVariant
|
|
47
|
+
desc: |
|
|
48
|
+
Contribution interface from a variant plugin. Registered via
|
|
49
|
+
`Capability.contributes(GameCapabilities.VariantProvider, variant)`.
|
|
50
|
+
fields:
|
|
51
|
+
id: string # stable variant id; matches the variant state typename
|
|
52
|
+
label: string # human-readable name shown in the picker
|
|
53
|
+
icon?: string # Phosphor icon name
|
|
54
|
+
variantSchema: Schema # Effect/Schema for the ECHO state object
|
|
55
|
+
inputSchema?: Schema # optional form schema shown after variant is selected
|
|
56
|
+
roles: string[] # player roles (e.g. ["white", "black"])
|
|
57
|
+
createVariant: Effect # factory that builds the variant ECHO state object
|
|
58
|
+
card?: ComponentType # optional Card surface component
|
|
59
|
+
article?: ComponentType # optional Article/Section surface component
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Components
|
|
63
|
+
|
|
64
|
+
```mdl
|
|
65
|
+
component CreateGamePanel
|
|
66
|
+
desc: |
|
|
67
|
+
Two-stage creation panel. Stage 1 shows a searchable variant picker.
|
|
68
|
+
Stage 2 (optional) shows a form rendered from the variant's inputSchema.
|
|
69
|
+
props:
|
|
70
|
+
target: Space | Obj
|
|
71
|
+
onCreateObject: (args: { variantId: string; input?: Record }) => void
|
|
72
|
+
variants?: GameVariant[] # override; defaults to GameCapabilities.VariantProvider
|
|
73
|
+
state:
|
|
74
|
+
selectedId?: string # id of the variant chosen in stage 1
|
|
75
|
+
actions:
|
|
76
|
+
selectVariant(id: string)
|
|
77
|
+
submitForm(input: Record)
|
|
78
|
+
layout: |
|
|
79
|
+
┌─────────────────────────┐
|
|
80
|
+
│ Stage 1: Variant Picker │
|
|
81
|
+
│ ┌───────────────────┐ │
|
|
82
|
+
│ │ Search input │ │
|
|
83
|
+
│ ├───────────────────┤ │
|
|
84
|
+
│ │ variant row │ │
|
|
85
|
+
│ │ variant row │ │
|
|
86
|
+
│ └───────────────────┘ │
|
|
87
|
+
├─────────────────────────┤
|
|
88
|
+
│ Stage 2: Input Form │
|
|
89
|
+
│ (rendered from schema) │
|
|
90
|
+
│ [Submit] │
|
|
91
|
+
└─────────────────────────┘
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```mdl
|
|
95
|
+
component GameArticle
|
|
96
|
+
desc: |
|
|
97
|
+
Article / Section surface for Game objects. Resolves the variant via
|
|
98
|
+
`game.variant` ref, finds the matching `GameVariant` by typename, and
|
|
99
|
+
delegates rendering to `variant.article`.
|
|
100
|
+
props:
|
|
101
|
+
game: Game
|
|
102
|
+
role?: string
|
|
103
|
+
attendableId?: string
|
|
104
|
+
state:
|
|
105
|
+
variant: Unknown # live variant state object from the Ref
|
|
106
|
+
match?: GameVariant # resolved GameVariant from capabilities
|
|
107
|
+
layout: |
|
|
108
|
+
┌─────────────────────────┐
|
|
109
|
+
│ [variant.article] │
|
|
110
|
+
│ (fallback: unsupported │
|
|
111
|
+
│ variant message) │
|
|
112
|
+
└─────────────────────────┘
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
```mdl
|
|
116
|
+
component GameCard
|
|
117
|
+
desc: |
|
|
118
|
+
Card surface for Game objects. Follows the same variant-resolution
|
|
119
|
+
logic as GameArticle and delegates to `variant.card`.
|
|
120
|
+
props:
|
|
121
|
+
game: Game
|
|
122
|
+
role?: string
|
|
123
|
+
state:
|
|
124
|
+
variant: Unknown
|
|
125
|
+
match?: GameVariant
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Operations
|
|
129
|
+
|
|
130
|
+
```mdl
|
|
131
|
+
op createGame
|
|
132
|
+
desc: |
|
|
133
|
+
Creates a variant state object via `variant.createVariant`, adds it
|
|
134
|
+
to the space as a hidden object, then wraps it in a top-level Game.
|
|
135
|
+
If the second write fails the variant state object is rolled back.
|
|
136
|
+
input:
|
|
137
|
+
variantId: string
|
|
138
|
+
input?: Record # variant-specific form values
|
|
139
|
+
target: Space | Obj
|
|
140
|
+
targetNodeId?: string
|
|
141
|
+
output: Game
|
|
142
|
+
errors:
|
|
143
|
+
UnknownVariant: no registered GameVariant matches variantId
|
|
144
|
+
effects: [echo:write]
|
|
145
|
+
requires: [GameCapabilities.VariantProvider, SpaceOperation.AddObject, SpaceOperation.RemoveObjects]
|
|
146
|
+
note: |
|
|
147
|
+
Invoked by CreateObjectEntry.createObject.
|
|
148
|
+
Variant state is added with `hidden: true` so it does not appear
|
|
149
|
+
as a top-level item in the user's space graph.
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Features
|
|
153
|
+
|
|
154
|
+
```mdl
|
|
155
|
+
feat F-1: Variant Registration
|
|
156
|
+
|
|
157
|
+
req F-1.1:
|
|
158
|
+
when: a variant plugin loads
|
|
159
|
+
then: it contributes a GameVariant via `Capability.contributes(GameCapabilities.VariantProvider, …)`
|
|
160
|
+
|
|
161
|
+
req F-1.2:
|
|
162
|
+
when: GameCapabilities.VariantProvider contributions are queried
|
|
163
|
+
then: all registered variants are returned as an ordered list
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```mdl
|
|
167
|
+
feat F-2: Game Creation
|
|
168
|
+
|
|
169
|
+
req F-2.1:
|
|
170
|
+
when: user opens the create-object flow for type `org.dxos.type.game`
|
|
171
|
+
then: CreateGamePanel is shown with a searchable list of available variants
|
|
172
|
+
|
|
173
|
+
req F-2.2:
|
|
174
|
+
when: user selects a variant with no inputSchema
|
|
175
|
+
then: op:createGame is invoked immediately with empty input
|
|
176
|
+
|
|
177
|
+
req F-2.3:
|
|
178
|
+
when: user selects a variant that has an inputSchema
|
|
179
|
+
then: a form rendered from the schema is shown; op:createGame is invoked on submit
|
|
180
|
+
|
|
181
|
+
req F-2.4:
|
|
182
|
+
when: op:createGame completes
|
|
183
|
+
then: a Game object appears in the space graph; variant state is hidden
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
```mdl
|
|
187
|
+
feat F-3: Game Article / Card Rendering
|
|
188
|
+
|
|
189
|
+
req F-3.1:
|
|
190
|
+
when: Composer navigates to a Game object
|
|
191
|
+
then: GameArticle resolves `game.variant` and delegates to the matching `variant.article`
|
|
192
|
+
|
|
193
|
+
req F-3.2:
|
|
194
|
+
when: a Game object is shown in a Card layout
|
|
195
|
+
then: GameCard resolves `game.variant` and delegates to `variant.card`
|
|
196
|
+
|
|
197
|
+
req F-3.3:
|
|
198
|
+
when: no matching variant is found for the loaded state object
|
|
199
|
+
then: GameArticle renders an "Unsupported game variant" fallback message
|
|
200
|
+
|
|
201
|
+
req F-3.4:
|
|
202
|
+
when: the variant ref is not yet loaded
|
|
203
|
+
then: neither GameArticle nor GameCard renders (returns null)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Acceptance
|
|
207
|
+
|
|
208
|
+
```mdl
|
|
209
|
+
test T-1: Variant picker shows all contributions
|
|
210
|
+
given: two variant plugins have contributed GameVariants
|
|
211
|
+
when: the user opens the create-game panel
|
|
212
|
+
then:
|
|
213
|
+
- CreateGamePanel lists both variants
|
|
214
|
+
- variants are sorted alphabetically
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
```mdl
|
|
218
|
+
test T-2: Create game without form
|
|
219
|
+
given: a variant with no inputSchema is selected
|
|
220
|
+
when: user clicks the variant item
|
|
221
|
+
then:
|
|
222
|
+
- onCreateObject is called immediately with { variantId }
|
|
223
|
+
- no form is shown
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
```mdl
|
|
227
|
+
test T-3: Create game with form
|
|
228
|
+
given: a variant with an inputSchema is selected
|
|
229
|
+
when: user fills and submits the form
|
|
230
|
+
then:
|
|
231
|
+
- onCreateObject is called with { variantId, input: <form values> }
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
```mdl
|
|
235
|
+
test T-4: Atomic game creation
|
|
236
|
+
given: a valid variantId and target space
|
|
237
|
+
when: op:createGame is invoked
|
|
238
|
+
then:
|
|
239
|
+
- variant state object added to space (hidden)
|
|
240
|
+
- Game object added with game.variant pointing to the state object
|
|
241
|
+
- both writes are visible to other peers via ECHO replication
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
```mdl
|
|
245
|
+
test T-5: Rollback on second write failure
|
|
246
|
+
given: variant state is successfully written
|
|
247
|
+
when: the second write (Game object) fails
|
|
248
|
+
then:
|
|
249
|
+
- RemoveObjects is invoked for the variant state
|
|
250
|
+
- no orphaned state object remains in the space
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
```mdl
|
|
254
|
+
test T-6: Unknown variant rejected
|
|
255
|
+
given: op:createGame is invoked with an unregistered variantId
|
|
256
|
+
when: the handler runs
|
|
257
|
+
then: UnknownVariant error is returned; no ECHO writes occur
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
```mdl
|
|
261
|
+
test T-7: Unsupported variant fallback
|
|
262
|
+
given: a Game whose variant typename has no registered GameVariant
|
|
263
|
+
when: GameArticle renders
|
|
264
|
+
then: "Unsupported game variant" message is shown instead of crashing
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Appendix: Extension Definitions
|
|
270
|
+
|
|
271
|
+
Extension block types used in this document are defined below using
|
|
272
|
+
the core `ext` primitive — the only construct the base language provides.
|
|
273
|
+
|
|
274
|
+
```mdl
|
|
275
|
+
ext type
|
|
276
|
+
uri: org.dxos.mdl.type@1.0
|
|
277
|
+
desc: A named data structure with typed fields and optional literals.
|
|
278
|
+
fields:
|
|
279
|
+
desc?: Prose
|
|
280
|
+
fields?: FieldMap # name[?]: TypeExpr (# inline comment)
|
|
281
|
+
literals?: UnionList # a | b | c
|
|
282
|
+
extends?: TypeRef[]
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
```mdl
|
|
286
|
+
ext feat
|
|
287
|
+
uri: org.dxos.mdl.feat@1.0
|
|
288
|
+
desc: A named feature grouping one or more requirements.
|
|
289
|
+
fields:
|
|
290
|
+
desc?: Prose
|
|
291
|
+
req: RequirementList
|
|
292
|
+
nesting: self # feat blocks may contain feat blocks
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
```mdl
|
|
296
|
+
ext test
|
|
297
|
+
uri: org.dxos.mdl.test@1.0
|
|
298
|
+
desc: An acceptance scenario expressed as given / when / then steps.
|
|
299
|
+
fields:
|
|
300
|
+
given?: Step | Step[]
|
|
301
|
+
when?: Step | Step[]
|
|
302
|
+
then: Step | Step[]
|
|
303
|
+
tags?: TagList
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
```mdl
|
|
307
|
+
ext component
|
|
308
|
+
uri: org.dxos.mdl.component@1.0
|
|
309
|
+
desc: A UI component with props, internal state, slots, actions, and events.
|
|
310
|
+
fields:
|
|
311
|
+
desc?: Prose
|
|
312
|
+
props?: FieldMap # external inputs (immutable inside component)
|
|
313
|
+
state?: FieldMap # internal reactive state
|
|
314
|
+
slots?: FieldMap # named ReactNode injection points
|
|
315
|
+
actions?: ActionMap # methods the component exposes or handles
|
|
316
|
+
emits?: EventMap # events the component raises to its parent
|
|
317
|
+
layout?: CodeBlock # ASCII sketch of visual structure (non-normative)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
```mdl
|
|
321
|
+
ext op
|
|
322
|
+
uri: org.dxos.mdl.op@1.0
|
|
323
|
+
desc: |
|
|
324
|
+
A named operation with typed inputs, outputs, and declared errors.
|
|
325
|
+
Pure ops have no effects or requires. Effectful ops declare both.
|
|
326
|
+
fields:
|
|
327
|
+
desc?: Prose
|
|
328
|
+
input?: FieldMap # named input parameters
|
|
329
|
+
output?: TypeExpr # return type
|
|
330
|
+
errors?: ErrorMap # name: Prose (when this error occurs)
|
|
331
|
+
effects?: EffectList # echo:read | echo:write | http | fs | ...
|
|
332
|
+
requires?: ServiceList # injected service dependencies
|
|
333
|
+
note?: Prose # implementation guidance (non-normative)
|
|
334
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import "./chunk-J5LGTIGS.mjs";
|
|
2
|
+
|
|
3
|
+
// src/containers/GameArticle/GameArticle.tsx
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { useCapabilities } from "@dxos/app-framework/ui";
|
|
6
|
+
import { Obj } from "@dxos/echo";
|
|
7
|
+
import { useObject } from "@dxos/echo-react";
|
|
8
|
+
import { useTranslation } from "@dxos/react-ui";
|
|
9
|
+
import { meta } from "#meta";
|
|
10
|
+
import { GameCapabilities } from "#types";
|
|
11
|
+
var GameArticle = ({ role, attendableId, subject: game }) => {
|
|
12
|
+
const { t } = useTranslation(meta.profile.key);
|
|
13
|
+
const variants = useCapabilities(GameCapabilities.VariantProvider);
|
|
14
|
+
const ref = game.variant;
|
|
15
|
+
useObject(ref);
|
|
16
|
+
const variant = ref?.target;
|
|
17
|
+
if (!variant) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const variantTypename = Obj.getTypename(variant);
|
|
21
|
+
const match = variants.find((v) => v.id === variantTypename);
|
|
22
|
+
if (!match?.article) {
|
|
23
|
+
return /* @__PURE__ */ React.createElement("div", {
|
|
24
|
+
className: "p-4 text-sm"
|
|
25
|
+
}, t("unsupported-variant.label", {
|
|
26
|
+
defaultValue: "Unsupported game variant"
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
29
|
+
const Component = match.article;
|
|
30
|
+
return /* @__PURE__ */ React.createElement(Component, {
|
|
31
|
+
game,
|
|
32
|
+
variant,
|
|
33
|
+
role,
|
|
34
|
+
attendableId
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
export {
|
|
38
|
+
GameArticle as default
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=GameArticle-AUJ4YDKW.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/containers/GameArticle/GameArticle.tsx"],
|
|
4
|
+
"sourcesContent": ["//\n// Copyright 2026 DXOS.org\n//\n\nimport React from 'react';\n\nimport { useCapabilities } from '@dxos/app-framework/ui';\nimport { type AppSurface } from '@dxos/app-toolkit/ui';\nimport { Obj, type Ref } from '@dxos/echo';\nimport { useObject } from '@dxos/echo-react';\nimport { useTranslation } from '@dxos/react-ui';\n\nimport { meta } from '#meta';\nimport { type Game, GameCapabilities } from '#types';\n\nexport type GameArticleProps = AppSurface.ObjectArticleProps<Game>;\n\nexport const GameArticle = ({ role, attendableId, subject: game }: GameArticleProps) => {\n const { t } = useTranslation(meta.profile.key);\n const variants = useCapabilities(GameCapabilities.VariantProvider);\n const ref = game.variant as Ref.Ref<Obj.Unknown>;\n // Subscribe via useObject so the article re-renders when the variant ref loads or changes.\n // The snapshot is discarded; we pass the live ref target to variant components so they can\n // subscribe to their own properties via useObject(state, 'prop').\n useObject(ref);\n const variant = ref?.target as Obj.Unknown | undefined;\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"],
|
|
5
|
+
"mappings": ";;;AAIA,OAAOA,WAAW;AAElB,SAASC,uBAAuB;AAEhC,SAASC,WAAqB;AAC9B,SAASC,iBAAiB;AAC1B,SAASC,sBAAsB;AAE/B,SAASC,YAAY;AACrB,SAAoBC,wBAAwB;AAIrC,IAAMC,cAAc,CAAC,EAAEC,MAAMC,cAAcC,SAASC,KAAI,MAAoB;AACjF,QAAM,EAAEC,EAAC,IAAKR,eAAeC,KAAKQ,QAAQC,GAAG;AAC7C,QAAMC,WAAWd,gBAAgBK,iBAAiBU,eAAe;AACjE,QAAMC,MAAMN,KAAKO;AAIjBf,YAAUc,GAAAA;AACV,QAAMC,UAAUD,KAAKE;AAErB,MAAI,CAACD,SAAS;AACZ,WAAO;EACT;AAEA,QAAME,kBAAkBlB,IAAImB,YAAYH,OAAAA;AACxC,QAAMI,QAAQP,SAASQ,KAAK,CAACC,MAAMA,EAAEC,OAAOL,eAAAA;AAC5C,MAAI,CAACE,OAAOI,SAAS;AACnB,WACE,sBAAA,cAACC,OAAAA;MAAIC,WAAU;OAAehB,EAAE,6BAA6B;MAAEiB,cAAc;IAA2B,CAAA,CAAA;EAE5G;AAEA,QAAMC,YAAYR,MAAMI;AACxB,SAAO,sBAAA,cAACI,WAAAA;IAAUnB;IAAYO;IAAkBV;IAAYC;;AAC9D;",
|
|
6
|
+
"names": ["React", "useCapabilities", "Obj", "useObject", "useTranslation", "meta", "GameCapabilities", "GameArticle", "role", "attendableId", "subject", "game", "t", "profile", "key", "variants", "VariantProvider", "ref", "variant", "target", "variantTypename", "getTypename", "match", "find", "v", "id", "article", "div", "className", "defaultValue", "Component"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import "./chunk-J5LGTIGS.mjs";
|
|
2
|
+
|
|
3
|
+
// src/containers/GameCard/GameCard.tsx
|
|
4
|
+
import React from "react";
|
|
5
|
+
import { useCapabilities } from "@dxos/app-framework/ui";
|
|
6
|
+
import { Obj } from "@dxos/echo";
|
|
7
|
+
import { useObject } from "@dxos/echo-react";
|
|
8
|
+
import { GameCapabilities } from "#types";
|
|
9
|
+
var GameCard = ({ role, subject: game }) => {
|
|
10
|
+
const variants = useCapabilities(GameCapabilities.VariantProvider);
|
|
11
|
+
const ref = game.variant;
|
|
12
|
+
useObject(ref);
|
|
13
|
+
const variant = ref?.target;
|
|
14
|
+
if (!variant) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const variantTypename = Obj.getTypename(variant);
|
|
18
|
+
const match = variants.find((v) => v.id === variantTypename);
|
|
19
|
+
if (!match?.card) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const Component = match.card;
|
|
23
|
+
return /* @__PURE__ */ React.createElement(Component, {
|
|
24
|
+
game,
|
|
25
|
+
variant,
|
|
26
|
+
role
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
export {
|
|
30
|
+
GameCard as default
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=GameCard-QBKHIL4Q.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/containers/GameCard/GameCard.tsx"],
|
|
4
|
+
"sourcesContent": ["//\n// Copyright 2026 DXOS.org\n//\n\nimport React from 'react';\n\nimport { useCapabilities } from '@dxos/app-framework/ui';\nimport { type AppSurface } from '@dxos/app-toolkit/ui';\nimport { Obj, type Ref } from '@dxos/echo';\nimport { useObject } from '@dxos/echo-react';\n\nimport { type Game, GameCapabilities } from '#types';\n\nexport type GameCardProps = AppSurface.ObjectCardProps<Game>;\n\nexport const GameCard = ({ role, subject: game }: GameCardProps) => {\n const variants = useCapabilities(GameCapabilities.VariantProvider);\n const ref = game.variant as Ref.Ref<Obj.Unknown>;\n // Subscribe for re-renders; pass the live ref target down to variant components.\n useObject(ref);\n const variant = ref?.target as Obj.Unknown | undefined;\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"],
|
|
5
|
+
"mappings": ";;;AAIA,OAAOA,WAAW;AAElB,SAASC,uBAAuB;AAEhC,SAASC,WAAqB;AAC9B,SAASC,iBAAiB;AAE1B,SAAoBC,wBAAwB;AAIrC,IAAMC,WAAW,CAAC,EAAEC,MAAMC,SAASC,KAAI,MAAiB;AAC7D,QAAMC,WAAWR,gBAAgBG,iBAAiBM,eAAe;AACjE,QAAMC,MAAMH,KAAKI;AAEjBT,YAAUQ,GAAAA;AACV,QAAMC,UAAUD,KAAKE;AAErB,MAAI,CAACD,SAAS;AACZ,WAAO;EACT;AAEA,QAAME,kBAAkBZ,IAAIa,YAAYH,OAAAA;AACxC,QAAMI,QAAQP,SAASQ,KAAK,CAACC,MAAMA,EAAEC,OAAOL,eAAAA;AAC5C,MAAI,CAACE,OAAOI,MAAM;AAChB,WAAO;EACT;AAEA,QAAMC,YAAYL,MAAMI;AACxB,SAAO,sBAAA,cAACC,WAAAA;IAAUb;IAAYI;IAAkBN;;AAClD;",
|
|
6
|
+
"names": ["React", "useCapabilities", "Obj", "useObject", "GameCapabilities", "GameCard", "role", "subject", "game", "variants", "VariantProvider", "ref", "variant", "target", "variantTypename", "getTypename", "match", "find", "v", "id", "card", "Component"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import "./chunk-J5LGTIGS.mjs";
|
|
2
|
+
|
|
3
|
+
// src/GamePlugin.tsx
|
|
4
|
+
import { Plugin } from "@dxos/app-framework";
|
|
5
|
+
import { AppPlugin } from "@dxos/app-toolkit";
|
|
6
|
+
import { CreateObject, ReactSurface } from "#capabilities";
|
|
7
|
+
import { meta } from "#meta";
|
|
8
|
+
import { translations } from "#translations";
|
|
9
|
+
import { Game } from "#types";
|
|
10
|
+
|
|
11
|
+
// raw-loader:/Users/jdw/Code/dxos/packages/plugins/plugin-game/PLUGIN.mdl
|
|
12
|
+
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 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 Stage 1: Variant Picker \u2502\n \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n \u2502 \u2502 Search input \u2502 \u2502\n \u2502 \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524 \u2502\n \u2502 \u2502 variant row \u2502 \u2502\n \u2502 \u2502 variant row \u2502 \u2502\n \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n \u2502 Stage 2: Input Form \u2502\n \u2502 (rendered from schema) \u2502\n \u2502 [Submit] \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\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 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 [variant.article] \u2502\n \u2502 (fallback: unsupported \u2502\n \u2502 variant message) \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\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, \u2026)`\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 \u2014 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';
|
|
13
|
+
|
|
14
|
+
// src/GamePlugin.tsx
|
|
15
|
+
var GamePlugin = Plugin.define(meta).pipe(AppPlugin.addCreateObjectModule({
|
|
16
|
+
activate: CreateObject
|
|
17
|
+
}), AppPlugin.addSchemaModule({
|
|
18
|
+
schema: [
|
|
19
|
+
Game
|
|
20
|
+
]
|
|
21
|
+
}), AppPlugin.addSurfaceModule({
|
|
22
|
+
activate: ReactSurface
|
|
23
|
+
}), AppPlugin.addTranslationsModule({
|
|
24
|
+
translations
|
|
25
|
+
}), AppPlugin.addPluginAssetModule({
|
|
26
|
+
asset: {
|
|
27
|
+
pluginId: meta.profile.key,
|
|
28
|
+
path: "PLUGIN.mdl",
|
|
29
|
+
content: PLUGIN_default,
|
|
30
|
+
mimeType: "application/x-mdl"
|
|
31
|
+
}
|
|
32
|
+
}), Plugin.make);
|
|
33
|
+
var GamePlugin_default = GamePlugin;
|
|
34
|
+
export {
|
|
35
|
+
GamePlugin,
|
|
36
|
+
GamePlugin_default as default
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=GamePlugin.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/GamePlugin.tsx", "raw-loader:/Users/jdw/Code/dxos/packages/plugins/plugin-game/PLUGIN.mdl"],
|
|
4
|
+
"sourcesContent": ["//\n// Copyright 2026 DXOS.org\n//\n\nimport { Plugin } from '@dxos/app-framework';\nimport { AppPlugin } from '@dxos/app-toolkit';\n\nimport { CreateObject, ReactSurface } from '#capabilities';\nimport { meta } from '#meta';\nimport { translations } from '#translations';\nimport { Game } from '#types';\n\n// eslint-disable-next-line import/no-relative-packages\nimport pluginSpec from '../PLUGIN.mdl?raw';\n\nexport const GamePlugin = Plugin.define(meta).pipe(\n AppPlugin.addCreateObjectModule({ activate: CreateObject }),\n AppPlugin.addSchemaModule({ schema: [Game] }),\n AppPlugin.addSurfaceModule({ activate: ReactSurface }),\n AppPlugin.addTranslationsModule({ translations }),\n AppPlugin.addPluginAssetModule({\n asset: { pluginId: meta.profile.key, path: 'PLUGIN.mdl', content: pluginSpec, mimeType: 'application/x-mdl' },\n }),\n Plugin.make,\n);\n\nexport default GamePlugin;\n", "---\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 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 Stage 1: Variant Picker \u2502\n \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n \u2502 \u2502 Search input \u2502 \u2502\n \u2502 \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524 \u2502\n \u2502 \u2502 variant row \u2502 \u2502\n \u2502 \u2502 variant row \u2502 \u2502\n \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n \u2502 Stage 2: Input Form \u2502\n \u2502 (rendered from schema) \u2502\n \u2502 [Submit] \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\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 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 [variant.article] \u2502\n \u2502 (fallback: unsupported \u2502\n \u2502 variant message) \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\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, \u2026)`\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 \u2014 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"],
|
|
5
|
+
"mappings": ";;;AAIA,SAASA,cAAc;AACvB,SAASC,iBAAiB;AAE1B,SAASC,cAAcC,oBAAoB;AAC3C,SAASC,YAAY;AACrB,SAASC,oBAAoB;AAC7B,SAASC,YAAY;;;ACVrB;;;ADeO,IAAMC,aAAaC,OAAOC,OAAOC,IAAAA,EAAMC,KAC5CC,UAAUC,sBAAsB;EAAEC,UAAUC;AAAa,CAAA,GACzDH,UAAUI,gBAAgB;EAAEC,QAAQ;IAACC;;AAAM,CAAA,GAC3CN,UAAUO,iBAAiB;EAAEL,UAAUM;AAAa,CAAA,GACpDR,UAAUS,sBAAsB;EAAEC;AAAa,CAAA,GAC/CV,UAAUW,qBAAqB;EAC7BC,OAAO;IAAEC,UAAUf,KAAKgB,QAAQC;IAAKC,MAAM;IAAcC,SAASC;IAAYC,UAAU;EAAoB;AAC9G,CAAA,GACAvB,OAAOwB,IAAI;AAGb,IAAA,qBAAezB;",
|
|
6
|
+
"names": ["Plugin", "AppPlugin", "CreateObject", "ReactSurface", "meta", "translations", "Game", "GamePlugin", "Plugin", "define", "meta", "pipe", "AppPlugin", "addCreateObjectModule", "activate", "CreateObject", "addSchemaModule", "schema", "Game", "addSurfaceModule", "ReactSurface", "addTranslationsModule", "translations", "addPluginAssetModule", "asset", "pluginId", "profile", "key", "path", "content", "pluginSpec", "mimeType", "make"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import "./chunk-J5LGTIGS.mjs";
|
|
2
|
+
|
|
3
|
+
// src/GamePlugin.node.tsx
|
|
4
|
+
import { Plugin } from "@dxos/app-framework";
|
|
5
|
+
import { AppPlugin } from "@dxos/app-toolkit";
|
|
6
|
+
import { meta } from "#meta";
|
|
7
|
+
import { translations } from "#translations";
|
|
8
|
+
import { Game } from "#types";
|
|
9
|
+
var GamePlugin = Plugin.define(meta).pipe(AppPlugin.addSchemaModule({
|
|
10
|
+
schema: [
|
|
11
|
+
Game
|
|
12
|
+
]
|
|
13
|
+
}), AppPlugin.addTranslationsModule({
|
|
14
|
+
translations
|
|
15
|
+
}), Plugin.make);
|
|
16
|
+
var GamePlugin_node_default = GamePlugin;
|
|
17
|
+
export {
|
|
18
|
+
GamePlugin,
|
|
19
|
+
GamePlugin_node_default as default
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=GamePlugin.node.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/GamePlugin.node.tsx"],
|
|
4
|
+
"sourcesContent": ["//\n// Copyright 2026 DXOS.org\n//\n\nimport { Plugin } from '@dxos/app-framework';\nimport { AppPlugin } from '@dxos/app-toolkit';\n\nimport { meta } from '#meta';\nimport { translations } from '#translations';\nimport { Game } from '#types';\n\n/**\n * Headless variant of GamePlugin (no React surfaces / CreateObject panel).\n * Used in node contexts (CLI, agents, tests) where rendering is unavailable —\n * the full variant lazy-loads `CreateGamePanel` from `react-ui-search` which\n * downstream bundlers don't resolve cleanly under nested pnpm symlinks.\n */\nexport const GamePlugin = Plugin.define(meta).pipe(\n AppPlugin.addSchemaModule({ schema: [Game] }),\n AppPlugin.addTranslationsModule({ translations }),\n Plugin.make,\n);\n\nexport default GamePlugin;\n"],
|
|
5
|
+
"mappings": ";;;AAIA,SAASA,cAAc;AACvB,SAASC,iBAAiB;AAE1B,SAASC,YAAY;AACrB,SAASC,oBAAoB;AAC7B,SAASC,YAAY;AAQd,IAAMC,aAAaL,OAAOM,OAAOJ,IAAAA,EAAMK,KAC5CN,UAAUO,gBAAgB;EAAEC,QAAQ;IAACL;;AAAM,CAAA,GAC3CH,UAAUS,sBAAsB;EAAEP;AAAa,CAAA,GAC/CH,OAAOW,IAAI;AAGb,IAAA,0BAAeN;",
|
|
6
|
+
"names": ["Plugin", "AppPlugin", "meta", "translations", "Game", "GamePlugin", "define", "pipe", "addSchemaModule", "schema", "addTranslationsModule", "make"]
|
|
7
|
+
}
|