@dxos/plugin-board 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.
Files changed (58) hide show
  1. package/PLUGIN.mdl +58 -9
  2. package/dist/lib/Board.mjs +4 -5
  3. package/dist/lib/Board.mjs.map +1 -1
  4. package/dist/lib/capabilities.browser.mjs +21 -0
  5. package/dist/lib/capabilities.browser.mjs.map +1 -0
  6. package/dist/lib/capabilities.mjs +1 -3
  7. package/dist/lib/capabilities.mjs.map +1 -1
  8. package/dist/lib/capabilities.node.mjs.map +1 -1
  9. package/dist/lib/capabilities.tauri.mjs +21 -0
  10. package/dist/lib/capabilities.tauri.mjs.map +1 -0
  11. package/dist/lib/capabilities.workerd.mjs.map +1 -1
  12. package/dist/lib/chunk-BoardArticle.mjs +68 -87
  13. package/dist/lib/chunk-BoardArticle.mjs.map +1 -1
  14. package/dist/lib/chunk-PLUGIN.mjs +6 -0
  15. package/dist/lib/chunk-PLUGIN.mjs.map +1 -0
  16. package/dist/lib/chunk-react-surface.mjs +2 -2
  17. package/dist/lib/chunk-react-surface.mjs.map +1 -1
  18. package/dist/lib/containers.mjs.map +1 -1
  19. package/dist/lib/index.mjs +0 -1
  20. package/dist/lib/meta.mjs.map +1 -1
  21. package/dist/lib/translations.mjs.map +1 -1
  22. package/dist/types/src/capabilities/gen/browser.d.ts +6 -0
  23. package/dist/types/src/capabilities/gen/browser.d.ts.map +1 -0
  24. package/dist/types/src/capabilities/gen/node.d.ts.map +1 -1
  25. package/dist/types/src/capabilities/gen/tauri.d.ts +6 -0
  26. package/dist/types/src/capabilities/gen/tauri.d.ts.map +1 -0
  27. package/dist/types/src/capabilities/gen/workerd.d.ts.map +1 -1
  28. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  29. package/dist/types/src/containers/BoardArticle/BoardArticle.d.ts +1 -1
  30. package/dist/types/src/containers/BoardArticle/BoardArticle.d.ts.map +1 -1
  31. package/dist/types/src/containers/BoardArticle/index.d.ts +1 -1
  32. package/dist/types/src/containers/BoardArticle/index.d.ts.map +1 -1
  33. package/dist/types/src/containers/index.d.ts.map +1 -1
  34. package/dist/types/src/index.d.ts +1 -2
  35. package/dist/types/src/index.d.ts.map +1 -1
  36. package/dist/types/src/translations.d.ts.map +1 -1
  37. package/dist/types/src/types/Board.d.ts +2 -2
  38. package/dist/types/src/types/Board.d.ts.map +1 -1
  39. package/dist/types/src/types/index.d.ts +2 -2
  40. package/dist/types/src/types/index.d.ts.map +1 -1
  41. package/dist/types/tsconfig.tsbuildinfo +1 -1
  42. package/package.json +34 -32
  43. package/src/capabilities/gen/browser.ts +31 -0
  44. package/src/capabilities/gen/node.ts +2 -2
  45. package/src/capabilities/gen/tauri.ts +31 -0
  46. package/src/capabilities/gen/workerd.ts +2 -2
  47. package/src/capabilities/index.ts +3 -3
  48. package/src/capabilities/react-surface.ts +2 -2
  49. package/src/containers/BoardArticle/BoardArticle.stories.tsx +5 -5
  50. package/src/containers/BoardArticle/BoardArticle.tsx +81 -86
  51. package/src/containers/BoardArticle/index.ts +1 -1
  52. package/src/containers/index.ts +1 -1
  53. package/src/index.ts +1 -2
  54. package/src/meta.ts +1 -1
  55. package/src/plugin.test.ts +2 -2
  56. package/src/translations.ts +2 -2
  57. package/src/types/Board.ts +4 -4
  58. package/src/types/index.ts +2 -2
package/PLUGIN.mdl CHANGED
@@ -16,9 +16,10 @@ Each extension is defined in the Appendix or resolved via its URI.
16
16
  |-------------|--------------------------------|
17
17
  | `type` | `org.dxos.mdl.type@1.0` |
18
18
  | `feat` | `org.dxos.mdl.feat@1.0` |
19
- | `test` | `org.dxos.mdl.test@1.0` |
19
+ | `scenario` | `org.dxos.mdl.scenario@1.0` |
20
20
  | `component` | `org.dxos.mdl.component@1.0` |
21
21
  | `op` | `org.dxos.mdl.op@1.0` |
22
+ | `test` | `org.dxos.mdl.test@1.0` |
22
23
 
23
24
  ## Types
24
25
 
@@ -213,7 +214,7 @@ feat F-6: Attention and Focus
213
214
  ## Acceptance
214
215
 
215
216
  ```mdl
216
- test T-1: New board is empty
217
+ scenario T-1: New board is empty
217
218
  given: a new Board has been created
218
219
  then:
219
220
  - board.items is empty
@@ -221,7 +222,7 @@ test T-1: New board is empty
221
222
  ```
222
223
 
223
224
  ```mdl
224
- test T-2: Add item via grid backdrop
225
+ scenario T-2: Add item via grid backdrop
225
226
  given: an empty board is displayed
226
227
  when: user clicks the "+" hotspot at position (100, 200)
227
228
  then:
@@ -231,7 +232,7 @@ test T-2: Add item via grid backdrop
231
232
  ```
232
233
 
233
234
  ```mdl
234
- test T-3: Add existing object via picker
235
+ scenario T-3: Add existing object via picker
235
236
  given: the space contains a Document with label "Meeting notes"
236
237
  when: user opens the ObjectPicker from the toolbar and selects "Meeting notes"
237
238
  then:
@@ -241,7 +242,7 @@ test T-3: Add existing object via picker
241
242
  ```
242
243
 
243
244
  ```mdl
244
- test T-4: Remove item
245
+ scenario T-4: Remove item
245
246
  given: board has one item
246
247
  when: user deletes the cell
247
248
  then:
@@ -251,7 +252,7 @@ test T-4: Remove item
251
252
  ```
252
253
 
253
254
  ```mdl
254
- test T-5: Move item
255
+ scenario T-5: Move item
255
256
  given: board has one item at position { x: 0, y: 0 }
256
257
  when: user drags it to { x: 300, y: 150 }
257
258
  then:
@@ -260,13 +261,61 @@ test T-5: Move item
260
261
  ```
261
262
 
262
263
  ```mdl
263
- test T-6: Toolbar gated by attention
264
+ scenario T-6: Toolbar gated by attention
264
265
  given: board is rendered without focus
265
266
  then: Board.Toolbar is disabled
266
267
  when: board gains focus
267
268
  then: Board.Toolbar is interactive
268
269
  ```
269
270
 
271
+ ## QA
272
+
273
+ Executable tests. Each step carries a human instruction (`do`) and, where the step maps to an
274
+ invocable operation, an agent binding (`invoke`). Report failures by test and step number.
275
+
276
+ None of the four `op` blocks above have a runtime counterpart: `createBoard` is `Board.makeBoard`
277
+ (a local factory, not an `Operation.make`) placed via the generic `space.addObject`, and
278
+ `addItem` / `removeItem` / `moveItem` are component actions that call `Obj.update` directly —
279
+ there is nothing to `invoke` for any of them. Every test below is `actors: human` for that reason.
280
+
281
+ ```mdl
282
+ test QA-1: Create a board, add and move an item, remove it
283
+ desc: The core board round-trip — create, add an existing object to the canvas, drag it, remove it.
284
+ actors: human
285
+ covers: [F-1.1, F-1.2, F-1.3, F-2.2, F-2.3, F-3.1, F-3.2, F-4.1, F-4.2]
286
+ status: unverified
287
+ given:
288
+ - an identity exists
289
+ - space: a space is open
290
+ - collection: a collection in that space, to create into
291
+ - a Document named "Meeting notes" exists in the space, for the ObjectPicker step
292
+ - no Board named "QA Board" exists — ABORT rather than deleting one this test did not create
293
+ steps:
294
+ - name: Create the board
295
+ do: In the navtree, click + on a collection and choose Board. Name it "QA Board".
296
+ expect: a Board appears under the collection, with an empty canvas
297
+ note: NO `invoke` — `Board.makeBoard` is a local factory, not an operation.
298
+ - name: Add "Meeting notes" via the picker
299
+ do: With no position given, click the toolbar "+"; the ObjectPicker opens listing space
300
+ objects other than the board. Select "Meeting notes".
301
+ expect: a cell for "Meeting notes" appears on the canvas at the default position (0, 0),
302
+ and the picker closes
303
+ note: NO `invoke` — `addItem` mutates `board.items`/`board.layout` directly in the component.
304
+ - name: Drag the cell to a new position
305
+ do: Drag the "Meeting notes" cell to a visibly different spot on the grid.
306
+ expect: the cell renders at the new position; no other cell moves
307
+ note: NO `invoke` — `moveItem` is a drag handler, not an operation.
308
+ after:
309
+ - name: Delete the board
310
+ do: Right-click "QA Board" in the navtree and choose Delete.
311
+ expect: the row leaves the navtree and its plank closes if it was open
312
+ note: |
313
+ NO `invoke` — the board is created by a UI gesture in step 1, which has no operation to
314
+ `capture` it from, so there is no bound value for `space.removeObjects` to name; deletion
315
+ stays human-only like the rest of this test. `space.removeObjects` deletes the Board object
316
+ itself; "Meeting notes" is unaffected — the board only referenced it.
317
+ ```
318
+
270
319
  ---
271
320
 
272
321
  ## Appendix: Extension Definitions
@@ -296,8 +345,8 @@ ext feat
296
345
  ```
297
346
 
298
347
  ```mdl
299
- ext test
300
- uri: org.dxos.mdl.test@1.0
348
+ ext scenario
349
+ uri: org.dxos.mdl.scenario@1.0
301
350
  desc: An acceptance scenario expressed as given / when / then steps.
302
351
  fields:
303
352
  given?: Step | Step[]
@@ -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 { BoardLayout, defaultLayout } from "@dxos/react-ui-board/types";
6
5
  //#region src/types/Board.ts
7
6
  var Board_exports = /* @__PURE__ */ __exportAll({
@@ -13,12 +12,12 @@ var Board_exports = /* @__PURE__ */ __exportAll({
13
12
  */
14
13
  var Board = class extends Type.makeObject(DXN.make("org.dxos.type.board", "0.1.0"))(Schema.Struct({
15
14
  name: Schema.String.pipe(Schema.optional),
16
- items: Ref.Ref(Obj.Unknown).pipe(Schema.Array, FormInputAnnotation.set(false)),
17
- layout: BoardLayout.pipe(FormInputAnnotation.set(false))
18
- }).pipe(LabelAnnotation.set(["name"]), Annotation.IconAnnotation.set({
15
+ items: Ref.Ref(Obj.Unknown).pipe(Schema.Array, Annotation.FormInputAnnotation.set(false)),
16
+ layout: BoardLayout.pipe(Annotation.FormInputAnnotation.set(false))
17
+ }).pipe(Annotation.LabelAnnotation.set(["name"]), Annotation.IconAnnotation.set({
19
18
  icon: "ph--squares-four--regular",
20
19
  hue: "green"
21
- }))) {};
20
+ }), Annotation.UserType.set())) {};
22
21
  var makeBoard = (props = {}) => Obj.make(Board, {
23
22
  items: [],
24
23
  layout: defaultLayout,
@@ -1 +1 @@
1
- {"version":3,"file":"Board.mjs","names":[],"sources":["../../src/types/Board.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Schema from 'effect/Schema';\n\nimport { Annotation, DXN, Obj, Ref, Type } from '@dxos/echo';\nimport { FormInputAnnotation, LabelAnnotation } from '@dxos/echo/Annotation';\nimport { BoardLayout, defaultLayout } from '@dxos/react-ui-board/types';\n\n/**\n * Board and layout.\n */\nexport class Board extends Type.makeObject<Board>(DXN.make('org.dxos.type.board', '0.1.0'))(\n Schema.Struct({\n name: Schema.String.pipe(Schema.optional),\n items: Ref.Ref(Obj.Unknown).pipe(Schema.Array, FormInputAnnotation.set(false)),\n layout: BoardLayout.pipe(FormInputAnnotation.set(false)),\n }).pipe(\n LabelAnnotation.set(['name']),\n Annotation.IconAnnotation.set({ icon: 'ph--squares-four--regular', hue: 'green' }),\n ),\n) {}\n\nexport const makeBoard = (props: Partial<Obj.MakeProps<typeof Board>> = {}) =>\n Obj.make(Board, {\n items: [],\n layout: defaultLayout,\n ...props,\n });\n"],"mappings":";;;;;;;;;;;;;AAaA,IAAa,QAAb,cAA2B,KAAK,WAAkB,IAAI,KAAK,uBAAuB,OAAO,CAAC,CAAC,CACzF,OAAO,OAAO;CACZ,MAAM,OAAO,OAAO,KAAK,OAAO,QAAQ;CACxC,OAAO,IAAI,IAAI,IAAI,OAAO,CAAC,CAAC,KAAK,OAAO,OAAO,oBAAoB,IAAI,KAAK,CAAC;CAC7E,QAAQ,YAAY,KAAK,oBAAoB,IAAI,KAAK,CAAC;AACzD,CAAC,CAAC,CAAC,KACD,gBAAgB,IAAI,CAAC,MAAM,CAAC,GAC5B,WAAW,eAAe,IAAI;CAAE,MAAM;CAA6B,KAAK;AAAQ,CAAC,CACnF,CACF,CAAC,CAAC,CAAC;AAEH,IAAa,aAAa,QAA8C,CAAC,MACvE,IAAI,KAAK,OAAO;CACd,OAAO,CAAC;CACR,QAAQ;CACR,GAAG;AACL,CAAC"}
1
+ {"version":3,"file":"Board.mjs","names":[],"sources":["../../src/types/Board.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as Schema from 'effect/Schema';\n\nimport { Annotation, DXN, Obj, Ref, Type } from '@dxos/echo';\nimport { BoardLayout, defaultLayout } from '@dxos/react-ui-board/types';\n\n/**\n * Board and layout.\n */\nexport class Board extends Type.makeObject<Board>(DXN.make('org.dxos.type.board', '0.1.0'))(\n Schema.Struct({\n name: Schema.String.pipe(Schema.optional),\n items: Ref.Ref(Obj.Unknown).pipe(Schema.Array, Annotation.FormInputAnnotation.set(false)),\n layout: BoardLayout.pipe(Annotation.FormInputAnnotation.set(false)),\n }).pipe(\n Annotation.LabelAnnotation.set(['name']),\n Annotation.IconAnnotation.set({ icon: 'ph--squares-four--regular', hue: 'green' }),\n Annotation.UserType.set(),\n ),\n) {}\n\nexport const makeBoard = (props: Partial<Obj.MakeProps<typeof Board>> = {}) =>\n Obj.make(Board, {\n items: [],\n layout: defaultLayout,\n ...props,\n });\n"],"mappings":";;;;;;;;;;;;AAYA,IAAa,QAAb,cAA2B,KAAK,WAAkB,IAAI,KAAK,uBAAuB,OAAO,CAAC,CAAC,CACzF,OAAO,OAAO;CACZ,MAAM,OAAO,OAAO,KAAK,OAAO,QAAQ;CACxC,OAAO,IAAI,IAAI,IAAI,OAAO,CAAC,CAAC,KAAK,OAAO,OAAO,WAAW,oBAAoB,IAAI,KAAK,CAAC;CACxF,QAAQ,YAAY,KAAK,WAAW,oBAAoB,IAAI,KAAK,CAAC;AACpE,CAAC,CAAC,CAAC,KACD,WAAW,gBAAgB,IAAI,CAAC,MAAM,CAAC,GACvC,WAAW,eAAe,IAAI;CAAE,MAAM;CAA6B,KAAK;AAAQ,CAAC,GACjF,WAAW,SAAS,IAAI,CAC1B,CACF,CAAC,CAAC,CAAC;AAEH,IAAa,aAAa,QAA8C,CAAC,MACvE,IAAI,KAAK,OAAO;CACd,OAAO,CAAC;CACR,QAAQ;CACR,GAAG;AACL,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { t as PLUGIN_default } from "./chunk-PLUGIN.mjs";
2
+ import * as AppCapability from "@dxos/app-toolkit/AppCapability";
3
+ import * as SpaceCapability from "@dxos/plugin-space/SpaceCapability";
4
+ import { translations } from "@dxos/react-ui-board/translations";
5
+ import { translations as translations$1 } from "#translations";
6
+ import { meta } from "#meta";
7
+ //#region src/capabilities/gen/browser.ts
8
+ var CreateObject = SpaceCapability.createObject(() => import("./chunk-create-object.mjs"));
9
+ var PluginAsset = AppCapability.pluginAsset({
10
+ pluginId: meta.profile.key,
11
+ path: "PLUGIN.mdl",
12
+ content: PLUGIN_default,
13
+ mimeType: "application/x-mdl"
14
+ });
15
+ var ReactSurface = AppCapability.surface(() => import("./chunk-react-surface.mjs"), { roles: ["org.dxos.role.article", "org.dxos.role.section"] });
16
+ var Schema = AppCapability.schema(() => import("./chunk-schema.mjs"));
17
+ var Translations = AppCapability.translations([...translations$1, ...translations]);
18
+ //#endregion
19
+ export { CreateObject, PluginAsset, ReactSurface, Schema, Translations };
20
+
21
+ //# 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';\nimport { translations as boardTranslations } from '@dxos/react-ui-board/translations';\n\nimport { meta } from '#meta';\nimport { translations } from '#translations';\n\nimport pluginSpec from '../../../PLUGIN.mdl?raw';\n\nexport const CreateObject = SpaceCapability.createObject(() => import('../create-object.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 ReactSurface = AppCapability.surface(() => import('../react-surface.ts'), {\n roles: ['org.dxos.role.article', 'org.dxos.role.section'],\n});\n\nexport const Schema = AppCapability.schema(() => import('../schema.ts'));\n\nexport const Translations = AppCapability.translations([...translations, ...boardTranslations]);\n"],"mappings":";;;;;;;AAeA,IAAa,eAAe,gBAAgB,mBAAmB,OAAO,4BAAsB;AAE5F,IAAa,cAAc,cAAc,YAAY;CACnD,UAAU,KAAK,QAAQ;CACvB,MAAM;CACN,SAAS;CACT,UAAU;AACZ,CAAC;AAED,IAAa,eAAe,cAAc,cAAc,OAAO,8BAAwB,EACrF,OAAO,CAAC,yBAAyB,uBAAuB,EAC1D,CAAC;AAED,IAAa,SAAS,cAAc,aAAa,OAAO,qBAAe;AAEvE,IAAa,eAAe,cAAc,aAAa,CAAC,GAAG,gBAAc,GAAG,YAAiB,CAAC"}
@@ -1,11 +1,9 @@
1
+ import { t as PLUGIN_default } from "./chunk-PLUGIN.mjs";
1
2
  import * as AppCapability from "@dxos/app-toolkit/AppCapability";
2
3
  import * as SpaceCapability from "@dxos/plugin-space/SpaceCapability";
3
4
  import { translations } from "@dxos/react-ui-board/translations";
4
5
  import { translations as translations$1 } from "#translations";
5
6
  import { meta } from "#meta";
6
- //#region PLUGIN.mdl?raw
7
- var PLUGIN_default = "---\nid: org.dxos.plugin.board\nname: BoardPlugin\nversion: 0.1.0\n---\n\nA spatial canvas plugin for `DXOS` Composer that lets users arrange any ECHO objects on an infinite two-dimensional grid.\nItems can be placed freely, moved by dragging, and removed from the canvas without deleting the underlying objects.\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 Position\n fields:\n x: number\n y: number\n w?: number\n h?: number\n```\n\n```mdl\ntype BoardLayout\n fields:\n cells: { [id: string]: Position }\n```\n\n```mdl\ntype Board\n desc: Root ECHO object (typename org.dxos.type.board).\n fields:\n name?: string\n items: Ref[] # ordered list of references to ECHO objects on the canvas\n layout: BoardLayout # position of every item keyed by object id\n```\n\n## Components\n\n```mdl\ncomponent BoardArticle\n desc: Full-canvas surface rendered for article and section roles.\n props:\n role: string # AppSurface role (article | section)\n subject: Board # ECHO Board object\n attendableId?: string # attention tracking id\n state:\n items: Obj.Unknown[] # resolved ECHO objects from board.items refs\n pickerState?: PickerState # null when picker closed; Position when open\n slots:\n toolbar?: ReactNode # Board.Toolbar (disabled when not focused)\n cell?: ReactNode # one Surface.Surface per item (Card role)\n actions:\n addItem(anchor, position?) → void\n deleteItem(id: string) → void\n moveItem(id: string, position: Position) → void\n selectFromPicker(id: string) → void\n emits:\n onAdd(anchor: HTMLButtonElement, position?: Position)\n onDelete(id: string)\n onMove(id: string, position: Position)\n layout: |\n ┌────────────────────────────────────┐\n │ Board.Toolbar (attention-gated) │\n ├────────────────────────────────────┤\n │ Board.Viewport │\n │ ┌──────────────────────────────┐ │\n │ │ Board.Backdrop (+) │ │\n │ │ │ │\n │ │ ┌──────┐ ┌──────┐ │ │\n │ │ │ Cell │ │ Cell │ … │ │\n │ │ └──────┘ └──────┘ │ │\n │ └──────────────────────────────┘ │\n ├────────────────────────────────────┤\n │ ObjectPicker (popover, when open) │\n └────────────────────────────────────┘\n```\n\n## Operations\n\n```mdl\nop createBoard\n desc: Creates a new Board ECHO object and adds it to the target space.\n input:\n name?: string\n target?: Space\n targetNodeId?: string\n output: Board\n effects: [echo:write]\n note: Delegates to SpaceOperation.AddObject with hidden=true.\n```\n\n```mdl\nop addItem\n desc: Adds an ECHO object reference to the board at the given grid position.\n input:\n board: Board\n object: Obj.Unknown\n position: Position\n output: Board\n effects: [echo:write]\n note: |\n When a grid \"+\" is clicked with a position, a new Markdown document is\n created inline. When the toolbar \"+\" is clicked without a position, an\n ObjectPicker is shown and the user selects an existing object.\n```\n\n```mdl\nop removeItem\n desc: Removes an item reference and its layout entry from the board.\n input:\n board: Board\n id: string\n output: Board\n effects: [echo:write]\n note: Does not delete the underlying ECHO object; only removes it from the canvas.\n```\n\n```mdl\nop moveItem\n desc: Updates the grid position for an existing board item.\n input:\n board: Board\n id: string\n position: Position\n output: Board\n effects: [echo:write]\n```\n\n## Features\n\n```mdl\nfeat F-1: Create Board\n\n req F-1.1: A new Board is created with an empty items list and an empty layout.\n req F-1.2: The Board is added to the active space using SpaceOperation.AddObject.\n req F-1.3: The Board appears in the space object list immediately after creation.\n```\n\n```mdl\nfeat F-2: Add Item to Board\n\n req F-2.1:\n when: user clicks \"+\" on the grid backdrop at a specific position\n then: a new Markdown document is created and placed at that position\n\n req F-2.2:\n when: user clicks \"+\" on the toolbar (no position)\n then: ObjectPicker opens, listing all space objects that are not the board itself\n\n req F-2.3:\n when: user selects an object from the ObjectPicker\n then: a Ref to that object is appended to board.items; layout entry created at default position\n\n req F-2.4: Objects are sorted alphabetically by label in the ObjectPicker.\n```\n\n```mdl\nfeat F-3: Remove Item from Board\n\n req F-3.1:\n when: user deletes a cell\n then: the Ref is removed from board.items and the layout entry is deleted\n note: the referenced ECHO object is not deleted\n\n req F-3.2: Board is updated atomically via Obj.update.\n```\n\n```mdl\nfeat F-4: Move Item on Board\n\n req F-4.1:\n when: user drags a card to a new position\n then: board.layout.cells[id] is updated with the new Position\n\n req F-4.2: Other items are not affected by a move operation.\n```\n\n```mdl\nfeat F-5: Render Items\n\n req F-5.1: Each item in board.items is rendered as a Surface.Surface with the Card role.\n req F-5.2:\n when: an item's Ref cannot be resolved (deleted object)\n then: the cell is silently omitted from the rendered canvas\n\n req F-5.3: Items are rendered in board.items order; z-ordering follows index.\n```\n\n```mdl\nfeat F-6: Attention and Focus\n\n req F-6.1:\n when: the Board surface does not have attention\n then: Board.Toolbar is rendered in disabled state; drag and add interactions are suppressed\n\n req F-6.2:\n when: the Board surface gains attention\n then: Board.Toolbar becomes interactive\n```\n\n## Acceptance\n\n```mdl\ntest T-1: New board is empty\n given: a new Board has been created\n then:\n - board.items is empty\n - board.layout.cells is empty\n```\n\n```mdl\ntest T-2: Add item via grid backdrop\n given: an empty board is displayed\n when: user clicks the \"+\" hotspot at position (100, 200)\n then:\n - a Markdown document is created in the space\n - board.items contains a Ref to the new document\n - board.layout.cells[doc.id] === { x: 100, y: 200 }\n```\n\n```mdl\ntest T-3: Add existing object via picker\n given: the space contains a Document with label \"Meeting notes\"\n when: user opens the ObjectPicker from the toolbar and selects \"Meeting notes\"\n then:\n - board.items contains a Ref to the Document\n - board.layout.cells[doc.id] === { x: 0, y: 0 }\n - picker is closed\n```\n\n```mdl\ntest T-4: Remove item\n given: board has one item\n when: user deletes the cell\n then:\n - board.items is empty\n - board.layout.cells is empty\n - the referenced ECHO object still exists in the space\n```\n\n```mdl\ntest T-5: Move item\n given: board has one item at position { x: 0, y: 0 }\n when: user drags it to { x: 300, y: 150 }\n then:\n - board.layout.cells[item.id] === { x: 300, y: 150 }\n - board.items length is unchanged\n```\n\n```mdl\ntest T-6: Toolbar gated by attention\n given: board is rendered without focus\n then: Board.Toolbar is disabled\n when: board gains focus\n then: Board.Toolbar is interactive\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";
8
- //#endregion
9
7
  //#region src/capabilities/index.ts
10
8
  var CreateObject = SpaceCapability.createObject(() => import("./chunk-create-object.mjs"));
11
9
  var PluginAsset = AppCapability.pluginAsset({
@@ -1 +1 @@
1
- {"version":3,"file":"capabilities.mjs","names":[],"sources":["../../src/capabilities/index.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as AppCapability from '@dxos/app-toolkit/AppCapability';\nimport * as SpaceCapability from '@dxos/plugin-space/SpaceCapability';\nimport { translations as boardTranslations } from '@dxos/react-ui-board/translations';\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\nexport const CreateObject = SpaceCapability.createObject(() => import('./create-object'));\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 ReactSurface = AppCapability.surface(() => import('./react-surface'), {\n roles: ['org.dxos.role.article', 'org.dxos.role.section'],\n});\nexport const Schema = AppCapability.schema(() => import('./schema'));\nexport const Translations = AppCapability.translations([...translations, ...boardTranslations]);\n"],"mappings":";;;;;;;;;AAcA,IAAa,eAAe,gBAAgB,mBAAmB,OAAO,4BAAkB;AACxF,IAAa,cAAc,cAAc,YAAY;CACnD,UAAU,KAAK,QAAQ;CACvB,MAAM;CACN,SAAS;CACT,UAAU;AACZ,CAAC;AACD,IAAa,eAAe,cAAc,cAAc,OAAO,8BAAoB,EACjF,OAAO,CAAC,yBAAyB,uBAAuB,EAC1D,CAAC;AACD,IAAa,SAAS,cAAc,aAAa,OAAO,qBAAW;AACnE,IAAa,eAAe,cAAc,aAAa,CAAC,GAAG,gBAAc,GAAG,YAAiB,CAAC"}
1
+ {"version":3,"file":"capabilities.mjs","names":[],"sources":["../../src/capabilities/index.ts"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport * as AppCapability from '@dxos/app-toolkit/AppCapability';\nimport * as SpaceCapability from '@dxos/plugin-space/SpaceCapability';\nimport { translations as boardTranslations } from '@dxos/react-ui-board/translations';\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\nexport const CreateObject = SpaceCapability.createObject(() => import('./create-object.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 ReactSurface = AppCapability.surface(() => import('./react-surface.ts'), {\n roles: ['org.dxos.role.article', 'org.dxos.role.section'],\n});\nexport const Schema = AppCapability.schema(() => import('./schema.ts'));\nexport const Translations = AppCapability.translations([...translations, ...boardTranslations]);\n"],"mappings":";;;;;;;AAcA,IAAa,eAAe,gBAAgB,mBAAmB,OAAO,4BAAqB;AAC3F,IAAa,cAAc,cAAc,YAAY;CACnD,UAAU,KAAK,QAAQ;CACvB,MAAM;CACN,SAAS;CACT,UAAU;AACZ,CAAC;AACD,IAAa,eAAe,cAAc,cAAc,OAAO,8BAAuB,EACpF,OAAO,CAAC,yBAAyB,uBAAuB,EAC1D,CAAC;AACD,IAAa,SAAS,cAAc,aAAa,OAAO,qBAAc;AACtE,IAAa,eAAe,cAAc,aAAa,CAAC,GAAG,gBAAc,GAAG,YAAiB,CAAC"}
@@ -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';\nimport * as SpaceCapability from '@dxos/plugin-space/SpaceCapability';\nimport { translations as boardTranslations } from '@dxos/react-ui-board/translations';\n\nimport { translations } from '#translations';\n\nexport const CreateObject = SpaceCapability.createObject(() => import('../create-object'));\n\nexport const Schema = AppCapability.schema(() => import('../schema'));\n\nexport const Translations = AppCapability.translations([...translations, ...boardTranslations]);\n\nexport const PluginAsset = undefined;\nexport const ReactSurface = undefined;\n"],"mappings":";;;;;AAYA,IAAa,eAAe,gBAAgB,mBAAmB,OAAO,4BAAmB;AAEzF,IAAa,SAAS,cAAc,aAAa,OAAO,qBAAY;AAEpE,IAAa,eAAe,cAAc,aAAa,CAAC,GAAG,gBAAc,GAAG,YAAiB,CAAC;AAE9F,IAAa,cAAc,KAAA;AAC3B,IAAa,eAAe,KAAA"}
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';\nimport * as SpaceCapability from '@dxos/plugin-space/SpaceCapability';\nimport { translations as boardTranslations } from '@dxos/react-ui-board/translations';\n\nimport { translations } from '#translations';\n\nexport const CreateObject = SpaceCapability.createObject(() => import('../create-object.ts'));\n\nexport const Schema = AppCapability.schema(() => import('../schema.ts'));\n\nexport const Translations = AppCapability.translations([...translations, ...boardTranslations]);\n\nexport const PluginAsset = undefined;\nexport const ReactSurface = undefined;\n"],"mappings":";;;;;AAYA,IAAa,eAAe,gBAAgB,mBAAmB,OAAO,4BAAsB;AAE5F,IAAa,SAAS,cAAc,aAAa,OAAO,qBAAe;AAEvE,IAAa,eAAe,cAAc,aAAa,CAAC,GAAG,gBAAc,GAAG,YAAiB,CAAC;AAE9F,IAAa,cAAc,KAAA;AAC3B,IAAa,eAAe,KAAA"}
@@ -0,0 +1,21 @@
1
+ import { t as PLUGIN_default } from "./chunk-PLUGIN.mjs";
2
+ import * as AppCapability from "@dxos/app-toolkit/AppCapability";
3
+ import * as SpaceCapability from "@dxos/plugin-space/SpaceCapability";
4
+ import { translations } from "@dxos/react-ui-board/translations";
5
+ import { translations as translations$1 } from "#translations";
6
+ import { meta } from "#meta";
7
+ //#region src/capabilities/gen/tauri.ts
8
+ var CreateObject = SpaceCapability.createObject(() => import("./chunk-create-object.mjs"));
9
+ var PluginAsset = AppCapability.pluginAsset({
10
+ pluginId: meta.profile.key,
11
+ path: "PLUGIN.mdl",
12
+ content: PLUGIN_default,
13
+ mimeType: "application/x-mdl"
14
+ });
15
+ var ReactSurface = AppCapability.surface(() => import("./chunk-react-surface.mjs"), { roles: ["org.dxos.role.article", "org.dxos.role.section"] });
16
+ var Schema = AppCapability.schema(() => import("./chunk-schema.mjs"));
17
+ var Translations = AppCapability.translations([...translations$1, ...translations]);
18
+ //#endregion
19
+ export { CreateObject, PluginAsset, ReactSurface, Schema, Translations };
20
+
21
+ //# 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';\nimport { translations as boardTranslations } from '@dxos/react-ui-board/translations';\n\nimport { meta } from '#meta';\nimport { translations } from '#translations';\n\nimport pluginSpec from '../../../PLUGIN.mdl?raw';\n\nexport const CreateObject = SpaceCapability.createObject(() => import('../create-object.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 ReactSurface = AppCapability.surface(() => import('../react-surface.ts'), {\n roles: ['org.dxos.role.article', 'org.dxos.role.section'],\n});\n\nexport const Schema = AppCapability.schema(() => import('../schema.ts'));\n\nexport const Translations = AppCapability.translations([...translations, ...boardTranslations]);\n"],"mappings":";;;;;;;AAeA,IAAa,eAAe,gBAAgB,mBAAmB,OAAO,4BAAsB;AAE5F,IAAa,cAAc,cAAc,YAAY;CACnD,UAAU,KAAK,QAAQ;CACvB,MAAM;CACN,SAAS;CACT,UAAU;AACZ,CAAC;AAED,IAAa,eAAe,cAAc,cAAc,OAAO,8BAAwB,EACrF,OAAO,CAAC,yBAAyB,uBAAuB,EAC1D,CAAC;AAED,IAAa,SAAS,cAAc,aAAa,OAAO,qBAAe;AAEvE,IAAa,eAAe,cAAc,aAAa,CAAC,GAAG,gBAAc,GAAG,YAAiB,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';\nimport * as SpaceCapability from '@dxos/plugin-space/SpaceCapability';\nimport { translations as boardTranslations } from '@dxos/react-ui-board/translations';\n\nimport { translations } from '#translations';\n\nexport const CreateObject = SpaceCapability.createObject(() => import('../create-object'));\n\nexport const Schema = AppCapability.schema(() => import('../schema'));\n\nexport const Translations = AppCapability.translations([...translations, ...boardTranslations]);\n\nexport const PluginAsset = undefined;\nexport const ReactSurface = undefined;\n"],"mappings":";;;;;AAYA,IAAa,eAAe,gBAAgB,mBAAmB,OAAO,4BAAmB;AAEzF,IAAa,SAAS,cAAc,aAAa,OAAO,qBAAY;AAEpE,IAAa,eAAe,cAAc,aAAa,CAAC,GAAG,gBAAc,GAAG,YAAiB,CAAC;AAE9F,IAAa,cAAc,KAAA;AAC3B,IAAa,eAAe,KAAA"}
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';\nimport * as SpaceCapability from '@dxos/plugin-space/SpaceCapability';\nimport { translations as boardTranslations } from '@dxos/react-ui-board/translations';\n\nimport { translations } from '#translations';\n\nexport const CreateObject = SpaceCapability.createObject(() => import('../create-object.ts'));\n\nexport const Schema = AppCapability.schema(() => import('../schema.ts'));\n\nexport const Translations = AppCapability.translations([...translations, ...boardTranslations]);\n\nexport const PluginAsset = undefined;\nexport const ReactSurface = undefined;\n"],"mappings":";;;;;AAYA,IAAa,eAAe,gBAAgB,mBAAmB,OAAO,4BAAsB;AAE5F,IAAa,SAAS,cAAc,aAAa,OAAO,qBAAe;AAEvE,IAAa,eAAe,cAAc,aAAa,CAAC,GAAG,gBAAc,GAAG,YAAiB,CAAC;AAE9F,IAAa,cAAc,KAAA;AAC3B,IAAa,eAAe,KAAA"}
@@ -2,18 +2,21 @@ import { translationKey } from "@dxos/react-ui-board/translations";
2
2
  import { useCallback, useMemo, useRef, useState } from "react";
3
3
  import { isNonNullable } from "@dxos/util";
4
4
  import { Filter, Obj, Ref } from "@dxos/echo";
5
- import { Surface } from "@dxos/app-framework/ui";
6
- import { AppSurface } from "@dxos/app-toolkit/ui";
5
+ import * as Surface from "@dxos/app-framework/Surface";
6
+ import * as AppSurface from "@dxos/app-toolkit/AppSurface";
7
7
  import { useAtomValue } from "@effect/atom-react/Hooks";
8
- import * as Atom from "effect/unstable/reactivity/Atom";
8
+ import * as Atom from "effect/reactivity/Atom";
9
9
  import { useObject, useQuery } from "@dxos/echo-react";
10
10
  import { invariant } from "@dxos/invariant";
11
11
  import { EID } from "@dxos/keys";
12
12
  import * as Markdown from "@dxos/plugin-markdown/Markdown";
13
- import { Panel, Toolbar, useTranslation } from "@dxos/react-ui";
14
13
  import { useAttention } from "@dxos/react-ui-attention";
15
14
  import { Board, resizeToFit } from "@dxos/react-ui-board";
16
15
  import { ObjectPicker } from "@dxos/react-ui-form";
16
+ import * as Button from "@dxos/react-ui/Button";
17
+ import * as Hooks from "@dxos/react-ui/Hooks";
18
+ import * as Panel from "@dxos/react-ui/Panel";
19
+ import * as Toolbar from "@dxos/react-ui/Toolbar";
17
20
  import { jsx, jsxs } from "react/jsx-runtime";
18
21
  //#region src/containers/BoardArticle/BoardArticle.tsx
19
22
  var __dxlog_file = "/__w/dxos/dxos/packages/plugins/plugin-board/src/containers/BoardArticle/BoardArticle.tsx";
@@ -33,7 +36,7 @@ var normalizeCells = (cells) => {
33
36
  }]));
34
37
  };
35
38
  var BoardArticle = ({ role, subject: board, attendableId }) => {
36
- const { t } = useTranslation(translationKey);
39
+ const { t } = Hooks.useTranslation(translationKey);
37
40
  const { hasAttention } = useAttention(attendableId);
38
41
  const db = Obj.getDatabase(board);
39
42
  const [boardItems] = useObject(board, "items");
@@ -47,8 +50,6 @@ var BoardArticle = ({ role, subject: board, attendableId }) => {
47
50
  }), [boardItems]);
48
51
  const items = useAtomValue(itemsAtom);
49
52
  const controller = useRef(null);
50
- const addTriggerRef = useRef(null);
51
- const [pickerState, setPickerState] = useState(null);
52
53
  const [zoom, setZoom] = useState(1);
53
54
  const layout = useMemo(() => ({ items: normalizeCells(board.layout.cells) }), [board.layout.cells]);
54
55
  const bounds = useMemo(() => ({
@@ -74,7 +75,7 @@ var BoardArticle = ({ role, subject: board, attendableId }) => {
74
75
  invariant(db, void 0, {
75
76
  "~LogMeta": "~LogMeta",
76
77
  F: __dxlog_file,
77
- L: 111,
78
+ L: 112,
78
79
  S: void 0,
79
80
  A: ["db", ""]
80
81
  });
@@ -95,93 +96,73 @@ var BoardArticle = ({ role, subject: board, attendableId }) => {
95
96
  });
96
97
  }, [board]);
97
98
  const handleSelect = useCallback((id) => {
98
- const position = pickerState?.position ?? DEFAULT_POSITION;
99
+ const position = DEFAULT_POSITION;
99
100
  const selected = objects.find((obj) => obj.id === id);
100
101
  if (!Obj.isObject(selected)) return;
101
102
  Obj.update(board, (board) => {
102
103
  board.items.push(Ref.make(selected));
103
104
  board.layout.cells[selected.id.toString()] = position;
104
105
  });
105
- setPickerState(null);
106
- }, [
107
- pickerState,
108
- objects,
109
- board
110
- ]);
111
- return /* @__PURE__ */ jsxs(ObjectPicker.Root, {
112
- open: !!pickerState,
113
- onOpenChange: (next) => setPickerState(next ? { position: DEFAULT_POSITION } : null),
114
- children: [
115
- /* @__PURE__ */ jsx(Board.Root, {
116
- ref: controller,
117
- layout,
118
- bounds,
119
- mode: "float",
120
- resolver: resizeToFit,
121
- zoom,
122
- onChange: handleChange,
123
- onAdd: handleAdd,
124
- onDelete: handleDelete,
125
- children: /* @__PURE__ */ jsxs(Panel.Root, {
126
- role,
127
- children: [/* @__PURE__ */ jsx(Panel.Toolbar, {
128
- asChild: true,
129
- children: /* @__PURE__ */ jsxs(Toolbar.Root, { children: [
130
- /* @__PURE__ */ jsx(Toolbar.IconButton, {
131
- icon: "ph--crosshair--regular",
132
- iconOnly: true,
133
- label: t("move-to-center.button"),
134
- disabled: !hasAttention,
135
- onClick: () => controller.current?.center()
136
- }),
137
- /* @__PURE__ */ jsx(Toolbar.IconButton, {
138
- icon: zoom < 1 ? "ph--arrows-in--regular" : "ph--arrows-out--regular",
139
- iconOnly: true,
140
- label: t("toggle-zoom.button"),
141
- disabled: !hasAttention,
142
- onClick: () => setZoom((value) => value < 1 ? 1 : .5)
143
- }),
144
- /* @__PURE__ */ jsx(Toolbar.IconButton, {
145
- icon: "ph--plus--regular",
146
- iconOnly: true,
147
- label: t("add-object.button"),
148
- disabled: !hasAttention,
149
- onClick: (event) => {
150
- addTriggerRef.current = event.currentTarget;
151
- setPickerState({ position: DEFAULT_POSITION });
152
- }
106
+ }, [objects, board]);
107
+ return /* @__PURE__ */ jsx(Board.Root, {
108
+ ref: controller,
109
+ layout,
110
+ bounds,
111
+ mode: "float",
112
+ resolver: resizeToFit,
113
+ zoom,
114
+ onChange: handleChange,
115
+ onAdd: handleAdd,
116
+ onDelete: handleDelete,
117
+ children: /* @__PURE__ */ jsxs(Panel.Root, {
118
+ role,
119
+ children: [/* @__PURE__ */ jsx(Panel.Header, { children: /* @__PURE__ */ jsxs(Toolbar.Root, { children: [
120
+ /* @__PURE__ */ jsx(Button.Root, {
121
+ icon: "ph--crosshair--regular",
122
+ iconOnly: true,
123
+ label: t("move-to-center.button"),
124
+ disabled: !hasAttention,
125
+ onClick: () => controller.current?.center()
126
+ }),
127
+ /* @__PURE__ */ jsx(Button.Root, {
128
+ icon: zoom < 1 ? "ph--arrows-in--regular" : "ph--arrows-out--regular",
129
+ iconOnly: true,
130
+ label: t("toggle-zoom.button"),
131
+ disabled: !hasAttention,
132
+ onClick: () => setZoom((value) => value < 1 ? 1 : .5)
133
+ }),
134
+ /* @__PURE__ */ jsx(ObjectPicker, {
135
+ options,
136
+ onSelect: handleSelect,
137
+ trigger: /* @__PURE__ */ jsx(Button.Root, {
138
+ icon: "ph--plus--regular",
139
+ iconOnly: true,
140
+ label: t("add-object.button"),
141
+ disabled: !hasAttention
142
+ })
143
+ })
144
+ ] }) }), /* @__PURE__ */ jsx(Panel.Body, {
145
+ asChild: true,
146
+ children: /* @__PURE__ */ jsxs(Board.Container, {
147
+ classNames: "dx-cover",
148
+ children: [/* @__PURE__ */ jsxs(Board.Viewport, { children: [/* @__PURE__ */ jsx(Board.Backdrop, {}), /* @__PURE__ */ jsx(Board.Content, { children: items?.map((item) => {
149
+ const itemLayout = layout.items[item.id];
150
+ return itemLayout ? /* @__PURE__ */ jsx(Board.Cell, {
151
+ item,
152
+ layout: itemLayout,
153
+ children: /* @__PURE__ */ jsx(Surface.Surface, {
154
+ type: AppSurface.CardContent,
155
+ data: {
156
+ subject: item,
157
+ editable: true
158
+ },
159
+ limit: 1
153
160
  })
154
- ] })
155
- }), /* @__PURE__ */ jsx(Panel.Content, {
156
- asChild: true,
157
- children: /* @__PURE__ */ jsxs(Board.Container, {
158
- classNames: "absolute inset-0",
159
- children: [/* @__PURE__ */ jsxs(Board.Viewport, { children: [/* @__PURE__ */ jsx(Board.Backdrop, {}), /* @__PURE__ */ jsx(Board.Content, { children: items?.map((item) => {
160
- const itemLayout = layout.items[item.id];
161
- return itemLayout ? /* @__PURE__ */ jsx(Board.Cell, {
162
- item,
163
- layout: itemLayout,
164
- children: /* @__PURE__ */ jsx(Surface.Surface, {
165
- type: AppSurface.CardContent,
166
- data: {
167
- subject: item,
168
- editable: true
169
- },
170
- limit: 1
171
- })
172
- }, item.id) : null;
173
- }) })] }), /* @__PURE__ */ jsx(Board.Map, { classNames: "absolute bottom-2 right-2 z-10 w-40" })]
174
- })
175
- })]
161
+ }, item.id) : null;
162
+ }) })] }), /* @__PURE__ */ jsx(Board.Map, { classNames: "absolute bottom-2 right-2 z-10 w-40" })]
176
163
  })
177
- }),
178
- /* @__PURE__ */ jsx(ObjectPicker.Content, {
179
- options,
180
- onSelect: handleSelect,
181
- classNames: "dx-card-popover-width"
182
- }),
183
- /* @__PURE__ */ jsx(ObjectPicker.VirtualTrigger, { virtualRef: addTriggerRef })
184
- ]
164
+ })]
165
+ })
185
166
  });
186
167
  };
187
168
  BoardArticle.displayName = "BoardArticle";
@@ -1 +1 @@
1
- {"version":3,"file":"chunk-BoardArticle.mjs","names":[],"sources":["../../src/containers/BoardArticle/BoardArticle.tsx"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport { useAtomValue } from '@effect/atom-react/Hooks';\nimport * as Atom from 'effect/unstable/reactivity/Atom';\nimport React, { useCallback, useMemo, useRef, useState } from 'react';\n\nimport { Surface } from '@dxos/app-framework/ui';\nimport { AppSurface } from '@dxos/app-toolkit/ui';\nimport { Filter, Obj, Ref } from '@dxos/echo';\nimport { useObject, useQuery } from '@dxos/echo-react';\nimport { invariant } from '@dxos/invariant';\nimport { EID } from '@dxos/keys';\nimport * as Markdown from '@dxos/plugin-markdown/Markdown';\nimport { Panel, Toolbar, useTranslation } from '@dxos/react-ui';\nimport { useAttention } from '@dxos/react-ui-attention';\nimport {\n Board as BoardComponent,\n type BoardController,\n type BoardRootProps,\n type Layout,\n resizeToFit,\n} from '@dxos/react-ui-board';\nimport { translationKey } from '@dxos/react-ui-board/translations';\nimport { ObjectPicker, type ObjectPickerContentProps } from '@dxos/react-ui-form';\nimport { isNonNullable } from '@dxos/util';\n\nimport { Board } from '#types';\n\ntype Position = { x: number; y: number };\n\nconst DEFAULT_POSITION: Position = { x: 0, y: 0 };\n\n// Legacy boards stored cells with a centre origin (signed coords); the engine is 0-based, so shift all\n// cells to non-negative before handing them to the component. Freshly-created boards are already 0-based.\nconst normalizeCells = (cells: Board.Board['layout']['cells']): Layout['items'] => {\n const values = Object.values(cells);\n const minX = Math.min(0, ...values.map((cell) => cell.x));\n const minY = Math.min(0, ...values.map((cell) => cell.y));\n if (minX === 0 && minY === 0) {\n return cells;\n }\n return Object.fromEntries(\n Object.entries(cells).map(([id, cell]) => [id, { ...cell, x: cell.x - minX, y: cell.y - minY }]),\n );\n};\n\nexport type BoardArticleProps = AppSurface.ObjectArticleProps<Board.Board>;\n\nexport const BoardArticle = ({ role, subject: board, attendableId }: BoardArticleProps) => {\n const { t } = useTranslation(translationKey);\n const { hasAttention } = useAttention(attendableId);\n const db = Obj.getDatabase(board);\n const [boardItems] = useObject(board, 'items');\n const itemsAtom = useMemo(\n () =>\n Atom.make((get) => {\n const result: Obj.Unknown[] = [];\n for (const ref of boardItems ?? []) {\n const obj = get(Obj.atomReactive(ref));\n if (obj) {\n result.push(obj);\n }\n }\n return result;\n }),\n [boardItems],\n );\n const items = useAtomValue(itemsAtom);\n\n const controller = useRef<BoardController>(null);\n const addTriggerRef = useRef<HTMLButtonElement | null>(null);\n const [pickerState, setPickerState] = useState<{ position: Position } | null>(null);\n const [zoom, setZoom] = useState(1);\n\n const layout = useMemo<Layout>(() => ({ items: normalizeCells(board.layout.cells) }), [board.layout.cells]);\n const bounds = useMemo(\n () => ({ columns: board.layout.size.width, rows: board.layout.size.height }),\n [board.layout.size.width, board.layout.size.height],\n );\n\n // TODO(burdon): Use search.\n const objects = useQuery(db, Filter.everything());\n const options = useMemo<ObjectPickerContentProps['options']>(\n () =>\n objects\n .filter((obj) => obj.id !== board.id)\n .map((obj) => {\n const label = Obj.getLabel(obj);\n return label ? { id: obj.id, label, hue: 'neutral' as const } : undefined;\n })\n .filter(isNonNullable)\n .sort(({ label: a }, { label: b }) => a.toLocaleLowerCase().localeCompare(b.toLocaleLowerCase())),\n [objects, board.id],\n );\n\n const handleChange = useCallback<NonNullable<BoardRootProps['onChange']>>(\n (next) => {\n Obj.update(board, (board) => {\n board.layout.cells = next.items;\n });\n },\n [board],\n );\n\n // Backdrop \"+\" supplies a position → create a new Markdown document directly.\n const handleAdd = useCallback<NonNullable<BoardRootProps['onAdd']>>(\n (position) => {\n const db = Obj.getDatabase(board);\n invariant(db);\n const doc = db.add(Markdown.make());\n Obj.update(board, (board) => {\n board.items.push(Ref.make(doc));\n board.layout.cells[doc.id.toString()] = position;\n });\n },\n [board],\n );\n\n // TODO(burdon): Use intents so can be undone.\n const handleDelete = useCallback<NonNullable<BoardRootProps['onDelete']>>(\n (id) => {\n // TODO(burdon): Impl. DXN.equals and pass in DXN from `id`.\n const idx = board.items.findIndex((ref) => {\n const echoUri = EID.tryParse(ref.uri);\n return (echoUri ? EID.getEntityId(echoUri) : undefined) === id;\n });\n Obj.update(board, (board) => {\n if (idx !== -1) {\n board.items.splice(idx, 1);\n }\n delete board.layout.cells[id];\n });\n },\n [board],\n );\n\n // Toolbar \"+\" adds an existing object via the picker.\n const handleSelect = useCallback<NonNullable<ObjectPickerContentProps['onSelect']>>(\n (id) => {\n const position = pickerState?.position ?? DEFAULT_POSITION;\n const selected = objects.find((obj) => obj.id === id);\n if (!Obj.isObject(selected)) {\n return;\n }\n Obj.update(board, (board) => {\n board.items.push(Ref.make(selected));\n board.layout.cells[selected.id.toString()] = position;\n });\n setPickerState(null);\n },\n [pickerState, objects, board],\n );\n\n return (\n <ObjectPicker.Root\n open={!!pickerState}\n onOpenChange={(next: boolean) => setPickerState(next ? { position: DEFAULT_POSITION } : null)}\n >\n <BoardComponent.Root\n ref={controller}\n layout={layout}\n bounds={bounds}\n mode='float'\n resolver={resizeToFit}\n zoom={zoom}\n onChange={handleChange}\n onAdd={handleAdd}\n onDelete={handleDelete}\n >\n <Panel.Root role={role}>\n {/* TODO(burdon): Migrate to Menu.Root + useMenuActions (threading attendableId). */}\n <Panel.Toolbar asChild>\n <Toolbar.Root>\n <Toolbar.IconButton\n icon='ph--crosshair--regular'\n iconOnly\n label={t('move-to-center.button')}\n disabled={!hasAttention}\n onClick={() => controller.current?.center()}\n />\n <Toolbar.IconButton\n icon={zoom < 1 ? 'ph--arrows-in--regular' : 'ph--arrows-out--regular'}\n iconOnly\n label={t('toggle-zoom.button')}\n disabled={!hasAttention}\n onClick={() => setZoom((value) => (value < 1 ? 1 : 0.5))}\n />\n <Toolbar.IconButton\n icon='ph--plus--regular'\n iconOnly\n label={t('add-object.button')}\n disabled={!hasAttention}\n onClick={(event) => {\n addTriggerRef.current = event.currentTarget as HTMLButtonElement;\n setPickerState({ position: DEFAULT_POSITION });\n }}\n />\n </Toolbar.Root>\n </Panel.Toolbar>\n <Panel.Content asChild>\n <BoardComponent.Container classNames='absolute inset-0'>\n <BoardComponent.Viewport>\n <BoardComponent.Backdrop />\n <BoardComponent.Content>\n {items?.map((item) => {\n const itemLayout = layout.items[item.id];\n return itemLayout ? (\n <BoardComponent.Cell item={item} key={item.id} layout={itemLayout}>\n <Surface.Surface\n type={AppSurface.CardContent}\n data={{ subject: item, editable: true }}\n limit={1}\n />\n </BoardComponent.Cell>\n ) : null;\n })}\n </BoardComponent.Content>\n </BoardComponent.Viewport>\n {/* Overview map (outlines the visible region), pinned to the corner over the board. */}\n <BoardComponent.Map classNames='absolute bottom-2 right-2 z-10 w-40' />\n </BoardComponent.Container>\n </Panel.Content>\n </Panel.Root>\n </BoardComponent.Root>\n <ObjectPicker.Content options={options} onSelect={handleSelect} classNames='dx-card-popover-width' />\n <ObjectPicker.VirtualTrigger virtualRef={addTriggerRef} />\n </ObjectPicker.Root>\n );\n};\n\nBoardArticle.displayName = 'BoardArticle';\n"],"mappings":"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"}
1
+ {"version":3,"file":"chunk-BoardArticle.mjs","names":[],"sources":["../../src/containers/BoardArticle/BoardArticle.tsx"],"sourcesContent":["//\n// Copyright 2025 DXOS.org\n//\n\nimport { useAtomValue } from '@effect/atom-react/Hooks';\nimport * as Atom from 'effect/reactivity/Atom';\nimport React, { useCallback, useMemo, useRef, useState } from 'react';\n\nimport * as Surface from '@dxos/app-framework/Surface';\nimport * as AppSurface from '@dxos/app-toolkit/AppSurface';\nimport { Filter, Obj, Ref } from '@dxos/echo';\nimport { useObject, useQuery } from '@dxos/echo-react';\nimport { invariant } from '@dxos/invariant';\nimport { EID } from '@dxos/keys';\nimport * as Markdown from '@dxos/plugin-markdown/Markdown';\nimport { useAttention } from '@dxos/react-ui-attention';\nimport {\n Board as BoardComponent,\n type BoardController,\n type BoardRootProps,\n type Layout,\n resizeToFit,\n} from '@dxos/react-ui-board';\nimport { translationKey } from '@dxos/react-ui-board/translations';\nimport { ObjectPicker, type ObjectPickerProps } from '@dxos/react-ui-form';\nimport * as Button from '@dxos/react-ui/Button';\nimport * as Hooks from '@dxos/react-ui/Hooks';\nimport * as Panel from '@dxos/react-ui/Panel';\nimport * as Toolbar from '@dxos/react-ui/Toolbar';\nimport { isNonNullable } from '@dxos/util';\n\nimport { Board } from '#types';\n\ntype Position = { x: number; y: number };\n\nconst DEFAULT_POSITION: Position = { x: 0, y: 0 };\n\n// Legacy boards stored cells with a centre origin (signed coords); the engine is 0-based, so shift all\n// cells to non-negative before handing them to the component. Freshly-created boards are already 0-based.\nconst normalizeCells = (cells: Board.Board['layout']['cells']): Layout['items'] => {\n const values = Object.values(cells);\n const minX = Math.min(0, ...values.map((cell) => cell.x));\n const minY = Math.min(0, ...values.map((cell) => cell.y));\n if (minX === 0 && minY === 0) {\n return cells;\n }\n return Object.fromEntries(\n Object.entries(cells).map(([id, cell]) => [id, { ...cell, x: cell.x - minX, y: cell.y - minY }]),\n );\n};\n\nexport type BoardArticleProps = AppSurface.ObjectArticleProps<Board.Board>;\n\nexport const BoardArticle = ({ role, subject: board, attendableId }: BoardArticleProps) => {\n const { t } = Hooks.useTranslation(translationKey);\n const { hasAttention } = useAttention(attendableId);\n const db = Obj.getDatabase(board);\n const [boardItems] = useObject(board, 'items');\n const itemsAtom = useMemo(\n () =>\n Atom.make((get) => {\n const result: Obj.Unknown[] = [];\n for (const ref of boardItems ?? []) {\n const obj = get(Obj.atomReactive(ref));\n if (obj) {\n result.push(obj);\n }\n }\n return result;\n }),\n [boardItems],\n );\n const items = useAtomValue(itemsAtom);\n\n const controller = useRef<BoardController>(null);\n const [zoom, setZoom] = useState(1);\n\n const layout = useMemo<Layout>(() => ({ items: normalizeCells(board.layout.cells) }), [board.layout.cells]);\n const bounds = useMemo(\n () => ({ columns: board.layout.size.width, rows: board.layout.size.height }),\n [board.layout.size.width, board.layout.size.height],\n );\n\n // TODO(burdon): Use search.\n const objects = useQuery(db, Filter.everything());\n const options = useMemo<ObjectPickerProps['options']>(\n () =>\n objects\n .filter((obj) => obj.id !== board.id)\n .map((obj) => {\n const label = Obj.getLabel(obj);\n return label ? { id: obj.id, label, hue: 'neutral' as const } : undefined;\n })\n .filter(isNonNullable)\n .sort(({ label: a }, { label: b }) => a.toLocaleLowerCase().localeCompare(b.toLocaleLowerCase())),\n [objects, board.id],\n );\n\n const handleChange = useCallback<NonNullable<BoardRootProps['onChange']>>(\n (next) => {\n Obj.update(board, (board) => {\n board.layout.cells = next.items;\n });\n },\n [board],\n );\n\n // Backdrop \"+\" supplies a position → create a new Markdown document directly.\n const handleAdd = useCallback<NonNullable<BoardRootProps['onAdd']>>(\n (position) => {\n const db = Obj.getDatabase(board);\n invariant(db);\n const doc = db.add(Markdown.make());\n Obj.update(board, (board) => {\n board.items.push(Ref.make(doc));\n board.layout.cells[doc.id.toString()] = position;\n });\n },\n [board],\n );\n\n // TODO(burdon): Use intents so can be undone.\n const handleDelete = useCallback<NonNullable<BoardRootProps['onDelete']>>(\n (id) => {\n // TODO(burdon): Impl. DXN.equals and pass in DXN from `id`.\n const idx = board.items.findIndex((ref) => {\n const echoUri = EID.tryParse(ref.uri);\n return (echoUri ? EID.getEntityId(echoUri) : undefined) === id;\n });\n Obj.update(board, (board) => {\n if (idx !== -1) {\n board.items.splice(idx, 1);\n }\n delete board.layout.cells[id];\n });\n },\n [board],\n );\n\n // Toolbar \"+\" adds an existing object via the picker.\n const handleSelect = useCallback(\n (id: string | undefined) => {\n const position = DEFAULT_POSITION;\n const selected = objects.find((obj) => obj.id === id);\n if (!Obj.isObject(selected)) {\n return;\n }\n Obj.update(board, (board) => {\n board.items.push(Ref.make(selected));\n board.layout.cells[selected.id.toString()] = position;\n });\n },\n [objects, board],\n );\n\n return (\n <BoardComponent.Root\n ref={controller}\n layout={layout}\n bounds={bounds}\n mode='float'\n resolver={resizeToFit}\n zoom={zoom}\n onChange={handleChange}\n onAdd={handleAdd}\n onDelete={handleDelete}\n >\n <Panel.Root role={role}>\n {/* TODO(burdon): Migrate to Menu.Root + useMenuActions (threading attendableId). */}\n <Panel.Header>\n <Toolbar.Root>\n <Button.Root\n icon='ph--crosshair--regular'\n iconOnly\n label={t('move-to-center.button')}\n disabled={!hasAttention}\n onClick={() => controller.current?.center()}\n />\n <Button.Root\n icon={zoom < 1 ? 'ph--arrows-in--regular' : 'ph--arrows-out--regular'}\n iconOnly\n label={t('toggle-zoom.button')}\n disabled={!hasAttention}\n onClick={() => setZoom((value) => (value < 1 ? 1 : 0.5))}\n />\n <ObjectPicker\n options={options}\n onSelect={handleSelect}\n trigger={\n <Button.Root\n icon='ph--plus--regular'\n iconOnly\n label={t('add-object.button')}\n disabled={!hasAttention}\n />\n }\n />\n </Toolbar.Root>\n </Panel.Header>\n <Panel.Body asChild>\n <BoardComponent.Container classNames='dx-cover'>\n <BoardComponent.Viewport>\n <BoardComponent.Backdrop />\n <BoardComponent.Content>\n {items?.map((item) => {\n const itemLayout = layout.items[item.id];\n return itemLayout ? (\n <BoardComponent.Cell item={item} key={item.id} layout={itemLayout}>\n <Surface.Surface\n type={AppSurface.CardContent}\n data={{ subject: item, editable: true }}\n limit={1}\n />\n </BoardComponent.Cell>\n ) : null;\n })}\n </BoardComponent.Content>\n </BoardComponent.Viewport>\n {/* Overview map (outlines the visible region), pinned to the corner over the board. */}\n <BoardComponent.Map classNames='absolute bottom-2 right-2 z-10 w-40' />\n </BoardComponent.Container>\n </Panel.Body>\n </Panel.Root>\n </BoardComponent.Root>\n );\n};\n\nBoardArticle.displayName = 'BoardArticle';\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ //#region PLUGIN.mdl?raw
2
+ var PLUGIN_default = "---\nid: org.dxos.plugin.board\nname: BoardPlugin\nversion: 0.1.0\n---\n\nA spatial canvas plugin for `DXOS` Composer that lets users arrange any ECHO objects on an infinite two-dimensional grid.\nItems can be placed freely, moved by dragging, and removed from the canvas without deleting the underlying objects.\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 Position\n fields:\n x: number\n y: number\n w?: number\n h?: number\n```\n\n```mdl\ntype BoardLayout\n fields:\n cells: { [id: string]: Position }\n```\n\n```mdl\ntype Board\n desc: Root ECHO object (typename org.dxos.type.board).\n fields:\n name?: string\n items: Ref[] # ordered list of references to ECHO objects on the canvas\n layout: BoardLayout # position of every item keyed by object id\n```\n\n## Components\n\n```mdl\ncomponent BoardArticle\n desc: Full-canvas surface rendered for article and section roles.\n props:\n role: string # AppSurface role (article | section)\n subject: Board # ECHO Board object\n attendableId?: string # attention tracking id\n state:\n items: Obj.Unknown[] # resolved ECHO objects from board.items refs\n pickerState?: PickerState # null when picker closed; Position when open\n slots:\n toolbar?: ReactNode # Board.Toolbar (disabled when not focused)\n cell?: ReactNode # one Surface.Surface per item (Card role)\n actions:\n addItem(anchor, position?) → void\n deleteItem(id: string) → void\n moveItem(id: string, position: Position) → void\n selectFromPicker(id: string) → void\n emits:\n onAdd(anchor: HTMLButtonElement, position?: Position)\n onDelete(id: string)\n onMove(id: string, position: Position)\n layout: |\n ┌────────────────────────────────────┐\n │ Board.Toolbar (attention-gated) │\n ├────────────────────────────────────┤\n │ Board.Viewport │\n │ ┌──────────────────────────────┐ │\n │ │ Board.Backdrop (+) │ │\n │ │ │ │\n │ │ ┌──────┐ ┌──────┐ │ │\n │ │ │ Cell │ │ Cell │ … │ │\n │ │ └──────┘ └──────┘ │ │\n │ └──────────────────────────────┘ │\n ├────────────────────────────────────┤\n │ ObjectPicker (popover, when open) │\n └────────────────────────────────────┘\n```\n\n## Operations\n\n```mdl\nop createBoard\n desc: Creates a new Board ECHO object and adds it to the target space.\n input:\n name?: string\n target?: Space\n targetNodeId?: string\n output: Board\n effects: [echo:write]\n note: Delegates to SpaceOperation.AddObject with hidden=true.\n```\n\n```mdl\nop addItem\n desc: Adds an ECHO object reference to the board at the given grid position.\n input:\n board: Board\n object: Obj.Unknown\n position: Position\n output: Board\n effects: [echo:write]\n note: |\n When a grid \"+\" is clicked with a position, a new Markdown document is\n created inline. When the toolbar \"+\" is clicked without a position, an\n ObjectPicker is shown and the user selects an existing object.\n```\n\n```mdl\nop removeItem\n desc: Removes an item reference and its layout entry from the board.\n input:\n board: Board\n id: string\n output: Board\n effects: [echo:write]\n note: Does not delete the underlying ECHO object; only removes it from the canvas.\n```\n\n```mdl\nop moveItem\n desc: Updates the grid position for an existing board item.\n input:\n board: Board\n id: string\n position: Position\n output: Board\n effects: [echo:write]\n```\n\n## Features\n\n```mdl\nfeat F-1: Create Board\n\n req F-1.1: A new Board is created with an empty items list and an empty layout.\n req F-1.2: The Board is added to the active space using SpaceOperation.AddObject.\n req F-1.3: The Board appears in the space object list immediately after creation.\n```\n\n```mdl\nfeat F-2: Add Item to Board\n\n req F-2.1:\n when: user clicks \"+\" on the grid backdrop at a specific position\n then: a new Markdown document is created and placed at that position\n\n req F-2.2:\n when: user clicks \"+\" on the toolbar (no position)\n then: ObjectPicker opens, listing all space objects that are not the board itself\n\n req F-2.3:\n when: user selects an object from the ObjectPicker\n then: a Ref to that object is appended to board.items; layout entry created at default position\n\n req F-2.4: Objects are sorted alphabetically by label in the ObjectPicker.\n```\n\n```mdl\nfeat F-3: Remove Item from Board\n\n req F-3.1:\n when: user deletes a cell\n then: the Ref is removed from board.items and the layout entry is deleted\n note: the referenced ECHO object is not deleted\n\n req F-3.2: Board is updated atomically via Obj.update.\n```\n\n```mdl\nfeat F-4: Move Item on Board\n\n req F-4.1:\n when: user drags a card to a new position\n then: board.layout.cells[id] is updated with the new Position\n\n req F-4.2: Other items are not affected by a move operation.\n```\n\n```mdl\nfeat F-5: Render Items\n\n req F-5.1: Each item in board.items is rendered as a Surface.Surface with the Card role.\n req F-5.2:\n when: an item's Ref cannot be resolved (deleted object)\n then: the cell is silently omitted from the rendered canvas\n\n req F-5.3: Items are rendered in board.items order; z-ordering follows index.\n```\n\n```mdl\nfeat F-6: Attention and Focus\n\n req F-6.1:\n when: the Board surface does not have attention\n then: Board.Toolbar is rendered in disabled state; drag and add interactions are suppressed\n\n req F-6.2:\n when: the Board surface gains attention\n then: Board.Toolbar becomes interactive\n```\n\n## Acceptance\n\n```mdl\nscenario T-1: New board is empty\n given: a new Board has been created\n then:\n - board.items is empty\n - board.layout.cells is empty\n```\n\n```mdl\nscenario T-2: Add item via grid backdrop\n given: an empty board is displayed\n when: user clicks the \"+\" hotspot at position (100, 200)\n then:\n - a Markdown document is created in the space\n - board.items contains a Ref to the new document\n - board.layout.cells[doc.id] === { x: 100, y: 200 }\n```\n\n```mdl\nscenario T-3: Add existing object via picker\n given: the space contains a Document with label \"Meeting notes\"\n when: user opens the ObjectPicker from the toolbar and selects \"Meeting notes\"\n then:\n - board.items contains a Ref to the Document\n - board.layout.cells[doc.id] === { x: 0, y: 0 }\n - picker is closed\n```\n\n```mdl\nscenario T-4: Remove item\n given: board has one item\n when: user deletes the cell\n then:\n - board.items is empty\n - board.layout.cells is empty\n - the referenced ECHO object still exists in the space\n```\n\n```mdl\nscenario T-5: Move item\n given: board has one item at position { x: 0, y: 0 }\n when: user drags it to { x: 300, y: 150 }\n then:\n - board.layout.cells[item.id] === { x: 300, y: 150 }\n - board.items length is unchanged\n```\n\n```mdl\nscenario T-6: Toolbar gated by attention\n given: board is rendered without focus\n then: Board.Toolbar is disabled\n when: board gains focus\n then: Board.Toolbar is interactive\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\nNone of the four `op` blocks above have a runtime counterpart: `createBoard` is `Board.makeBoard`\n(a local factory, not an `Operation.make`) placed via the generic `space.addObject`, and\n`addItem` / `removeItem` / `moveItem` are component actions that call `Obj.update` directly —\nthere is nothing to `invoke` for any of them. Every test below is `actors: human` for that reason.\n\n```mdl\ntest QA-1: Create a board, add and move an item, remove it\n desc: The core board round-trip — create, add an existing object to the canvas, drag it, remove it.\n actors: human\n covers: [F-1.1, F-1.2, F-1.3, F-2.2, F-2.3, F-3.1, F-3.2, F-4.1, F-4.2]\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 - a Document named \"Meeting notes\" exists in the space, for the ObjectPicker step\n - no Board named \"QA Board\" exists — ABORT rather than deleting one this test did not create\n steps:\n - name: Create the board\n do: In the navtree, click + on a collection and choose Board. Name it \"QA Board\".\n expect: a Board appears under the collection, with an empty canvas\n note: NO `invoke` — `Board.makeBoard` is a local factory, not an operation.\n - name: Add \"Meeting notes\" via the picker\n do: With no position given, click the toolbar \"+\"; the ObjectPicker opens listing space\n objects other than the board. Select \"Meeting notes\".\n expect: a cell for \"Meeting notes\" appears on the canvas at the default position (0, 0),\n and the picker closes\n note: NO `invoke` — `addItem` mutates `board.items`/`board.layout` directly in the component.\n - name: Drag the cell to a new position\n do: Drag the \"Meeting notes\" cell to a visibly different spot on the grid.\n expect: the cell renders at the new position; no other cell moves\n note: NO `invoke` — `moveItem` is a drag handler, not an operation.\n after:\n - name: Delete the board\n do: Right-click \"QA Board\" in the navtree and choose Delete.\n expect: the row leaves the navtree and its plank closes if it was open\n note: |\n NO `invoke` — the board is created by a UI gesture in step 1, which has no operation to\n `capture` it from, so there is no bound value for `space.removeObjects` to name; deletion\n stays human-only like the rest of this test. `space.removeObjects` deletes the Board object\n itself; \"Meeting notes\" is unaffected — the board only referenced it.\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":""}
@@ -2,8 +2,8 @@ import { Board } from "#types";
2
2
  import * as Effect from "effect/Effect";
3
3
  import * as Capability from "@dxos/app-framework/Capability";
4
4
  import * as Capabilities from "@dxos/app-framework/Capabilities";
5
- import { Surface } from "@dxos/app-framework/ui";
6
- import { AppSurface } from "@dxos/app-toolkit/ui";
5
+ import * as Surface from "@dxos/app-framework/Surface";
6
+ import * as AppSurface from "@dxos/app-toolkit/AppSurface";
7
7
  import { BoardArticle } 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({