@dxos/plugin-crx 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.
Files changed (168) hide show
  1. package/LICENSE +105 -0
  2. package/PLUGIN.mdl +599 -0
  3. package/README.md +25 -0
  4. package/dist/lib/CrxCapabilities.mjs +21 -0
  5. package/dist/lib/CrxCapabilities.mjs.map +1 -0
  6. package/dist/lib/CrxEvents.mjs +15 -0
  7. package/dist/lib/CrxEvents.mjs.map +1 -0
  8. package/dist/lib/CrxOperation.mjs +50 -0
  9. package/dist/lib/CrxOperation.mjs.map +1 -0
  10. package/dist/lib/CrxPlugin.mjs +16 -0
  11. package/dist/lib/CrxPlugin.mjs.map +1 -0
  12. package/dist/lib/PageAction.mjs +35 -0
  13. package/dist/lib/PageAction.mjs.map +1 -0
  14. package/dist/lib/Settings.mjs +84 -0
  15. package/dist/lib/Settings.mjs.map +1 -0
  16. package/dist/lib/capabilities.mjs +41 -0
  17. package/dist/lib/capabilities.mjs.map +1 -0
  18. package/dist/lib/capabilities.node.mjs +32 -0
  19. package/dist/lib/capabilities.node.mjs.map +1 -0
  20. package/dist/lib/capabilities.workerd.mjs +32 -0
  21. package/dist/lib/capabilities.workerd.mjs.map +1 -0
  22. package/dist/lib/chunk-add-note-from-snapshot.mjs +15 -0
  23. package/dist/lib/chunk-add-note-from-snapshot.mjs.map +1 -0
  24. package/dist/lib/chunk-add-organization-from-snapshot.mjs +15 -0
  25. package/dist/lib/chunk-add-organization-from-snapshot.mjs.map +1 -0
  26. package/dist/lib/chunk-add-person-from-snapshot.mjs +15 -0
  27. package/dist/lib/chunk-add-person-from-snapshot.mjs.map +1 -0
  28. package/dist/lib/chunk-install-page-actions.mjs +284 -0
  29. package/dist/lib/chunk-install-page-actions.mjs.map +1 -0
  30. package/dist/lib/chunk-mapping.mjs +81 -0
  31. package/dist/lib/chunk-mapping.mjs.map +1 -0
  32. package/dist/lib/chunk-operation-handler.mjs +12 -0
  33. package/dist/lib/chunk-operation-handler.mjs.map +1 -0
  34. package/dist/lib/chunk-page-action-provider.mjs +41 -0
  35. package/dist/lib/chunk-page-action-provider.mjs.map +1 -0
  36. package/dist/lib/chunk-react-surface.mjs +18 -0
  37. package/dist/lib/chunk-react-surface.mjs.map +1 -0
  38. package/dist/lib/chunk-rolldown-runtime.mjs +13 -0
  39. package/dist/lib/chunk-settings.mjs +28 -0
  40. package/dist/lib/chunk-settings.mjs.map +1 -0
  41. package/dist/lib/containers.mjs +80 -0
  42. package/dist/lib/containers.mjs.map +1 -0
  43. package/dist/lib/index.mjs +4 -0
  44. package/dist/lib/meta.mjs +34 -0
  45. package/dist/lib/meta.mjs.map +1 -0
  46. package/dist/lib/operations.mjs +13 -0
  47. package/dist/lib/operations.mjs.map +1 -0
  48. package/dist/lib/plugin.mjs +9 -0
  49. package/dist/lib/plugin.mjs.map +1 -0
  50. package/dist/lib/translations.mjs +22 -0
  51. package/dist/lib/translations.mjs.map +1 -0
  52. package/dist/lib/types.mjs +6 -0
  53. package/dist/lib/util.mjs +63 -0
  54. package/dist/lib/util.mjs.map +1 -0
  55. package/dist/types/dx.config.d.ts +28 -0
  56. package/dist/types/dx.config.d.ts.map +1 -0
  57. package/dist/types/src/CrxPlugin.d.ts +37 -0
  58. package/dist/types/src/CrxPlugin.d.ts.map +1 -0
  59. package/dist/types/src/capabilities/gen/node.d.ts +9 -0
  60. package/dist/types/src/capabilities/gen/node.d.ts.map +1 -0
  61. package/dist/types/src/capabilities/gen/workerd.d.ts +9 -0
  62. package/dist/types/src/capabilities/gen/workerd.d.ts.map +1 -0
  63. package/dist/types/src/capabilities/index.d.ts +9 -0
  64. package/dist/types/src/capabilities/index.d.ts.map +1 -0
  65. package/dist/types/src/capabilities/install-page-actions.d.ts +5 -0
  66. package/dist/types/src/capabilities/install-page-actions.d.ts.map +1 -0
  67. package/dist/types/src/capabilities/operation-handler.d.ts +5 -0
  68. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  69. package/dist/types/src/capabilities/page-action-provider.d.ts +5 -0
  70. package/dist/types/src/capabilities/page-action-provider.d.ts.map +1 -0
  71. package/dist/types/src/capabilities/react-surface.d.ts +5 -0
  72. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  73. package/dist/types/src/capabilities/settings.d.ts +10 -0
  74. package/dist/types/src/capabilities/settings.d.ts.map +1 -0
  75. package/dist/types/src/containers/CrxSettings/CrxSettings.d.ts +10 -0
  76. package/dist/types/src/containers/CrxSettings/CrxSettings.d.ts.map +1 -0
  77. package/dist/types/src/containers/CrxSettings/CrxSettings.stories.d.ts +25 -0
  78. package/dist/types/src/containers/CrxSettings/CrxSettings.stories.d.ts.map +1 -0
  79. package/dist/types/src/containers/CrxSettings/index.d.ts +2 -0
  80. package/dist/types/src/containers/CrxSettings/index.d.ts.map +1 -0
  81. package/dist/types/src/containers/index.d.ts +2 -0
  82. package/dist/types/src/containers/index.d.ts.map +1 -0
  83. package/dist/types/src/index.d.ts +4 -0
  84. package/dist/types/src/index.d.ts.map +1 -0
  85. package/dist/types/src/mapping.d.ts +24 -0
  86. package/dist/types/src/mapping.d.ts.map +1 -0
  87. package/dist/types/src/mapping.test.d.ts +2 -0
  88. package/dist/types/src/mapping.test.d.ts.map +1 -0
  89. package/dist/types/src/meta.d.ts +33 -0
  90. package/dist/types/src/meta.d.ts.map +1 -0
  91. package/dist/types/src/operations/add-note-from-snapshot.d.ts +5 -0
  92. package/dist/types/src/operations/add-note-from-snapshot.d.ts.map +1 -0
  93. package/dist/types/src/operations/add-organization-from-snapshot.d.ts +5 -0
  94. package/dist/types/src/operations/add-organization-from-snapshot.d.ts.map +1 -0
  95. package/dist/types/src/operations/add-person-from-snapshot.d.ts +5 -0
  96. package/dist/types/src/operations/add-person-from-snapshot.d.ts.map +1 -0
  97. package/dist/types/src/operations/index.d.ts +3 -0
  98. package/dist/types/src/operations/index.d.ts.map +1 -0
  99. package/dist/types/src/page-actions.d.ts +39 -0
  100. package/dist/types/src/page-actions.d.ts.map +1 -0
  101. package/dist/types/src/page-actions.test.d.ts +2 -0
  102. package/dist/types/src/page-actions.test.d.ts.map +1 -0
  103. package/dist/types/src/plugin.d.ts +4 -0
  104. package/dist/types/src/plugin.d.ts.map +1 -0
  105. package/dist/types/src/stories/Testbench.stories.d.ts +8 -0
  106. package/dist/types/src/stories/Testbench.stories.d.ts.map +1 -0
  107. package/dist/types/src/translations.d.ts +22 -0
  108. package/dist/types/src/translations.d.ts.map +1 -0
  109. package/dist/types/src/types/CrxCapabilities.d.ts +12 -0
  110. package/dist/types/src/types/CrxCapabilities.d.ts.map +1 -0
  111. package/dist/types/src/types/CrxEvents.d.ts +8 -0
  112. package/dist/types/src/types/CrxEvents.d.ts.map +1 -0
  113. package/dist/types/src/types/CrxOperation.d.ts +108 -0
  114. package/dist/types/src/types/CrxOperation.d.ts.map +1 -0
  115. package/dist/types/src/types/PageAction.d.ts +153 -0
  116. package/dist/types/src/types/PageAction.d.ts.map +1 -0
  117. package/dist/types/src/types/Settings.d.ts +25 -0
  118. package/dist/types/src/types/Settings.d.ts.map +1 -0
  119. package/dist/types/src/types/Settings.test.d.ts +2 -0
  120. package/dist/types/src/types/Settings.test.d.ts.map +1 -0
  121. package/dist/types/src/types/index.d.ts +6 -0
  122. package/dist/types/src/types/index.d.ts.map +1 -0
  123. package/dist/types/src/util/index.d.ts +2 -0
  124. package/dist/types/src/util/index.d.ts.map +1 -0
  125. package/dist/types/src/util/pingExtension.d.ts +15 -0
  126. package/dist/types/src/util/pingExtension.d.ts.map +1 -0
  127. package/dist/types/src/util/pingExtension.test.d.ts +2 -0
  128. package/dist/types/src/util/pingExtension.test.d.ts.map +1 -0
  129. package/dist/types/tsconfig.tsbuildinfo +1 -0
  130. package/dx.config.ts +32 -0
  131. package/package.json +162 -0
  132. package/src/CrxPlugin.ts +15 -0
  133. package/src/capabilities/gen/node.ts +43 -0
  134. package/src/capabilities/gen/workerd.ts +43 -0
  135. package/src/capabilities/index.ts +47 -0
  136. package/src/capabilities/install-page-actions.ts +64 -0
  137. package/src/capabilities/operation-handler.ts +16 -0
  138. package/src/capabilities/page-action-provider.ts +47 -0
  139. package/src/capabilities/react-surface.ts +26 -0
  140. package/src/capabilities/settings.ts +36 -0
  141. package/src/containers/CrxSettings/CrxSettings.stories.tsx +99 -0
  142. package/src/containers/CrxSettings/CrxSettings.tsx +100 -0
  143. package/src/containers/CrxSettings/index.ts +5 -0
  144. package/src/containers/index.ts +5 -0
  145. package/src/index.ts +7 -0
  146. package/src/mapping.test.ts +87 -0
  147. package/src/mapping.ts +101 -0
  148. package/src/meta.ts +9 -0
  149. package/src/operations/add-note-from-snapshot.ts +24 -0
  150. package/src/operations/add-organization-from-snapshot.ts +29 -0
  151. package/src/operations/add-person-from-snapshot.ts +29 -0
  152. package/src/operations/index.ts +16 -0
  153. package/src/page-actions.test.ts +164 -0
  154. package/src/page-actions.ts +171 -0
  155. package/src/plugin.tsx +29 -0
  156. package/src/stories/Testbench.stories.tsx +45 -0
  157. package/src/translations.ts +31 -0
  158. package/src/types/CrxCapabilities.ts +26 -0
  159. package/src/types/CrxEvents.ts +16 -0
  160. package/src/types/CrxOperation.ts +54 -0
  161. package/src/types/PageAction.ts +57 -0
  162. package/src/types/Settings.test.ts +30 -0
  163. package/src/types/Settings.ts +101 -0
  164. package/src/types/index.ts +9 -0
  165. package/src/util/index.ts +5 -0
  166. package/src/util/pingExtension.test.ts +69 -0
  167. package/src/util/pingExtension.ts +83 -0
  168. 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,599 @@
1
+ ---
2
+ id: org.dxos.plugin.crx
3
+ name: CrxPlugin
4
+ version: 0.3.0
5
+ ---
6
+
7
+ Coordinates with the `@dxos/composer-crx` browser extension. Owns the Composer
8
+ side of the page-actions bridge: plugins contribute page actions (serializable
9
+ descriptor + target operation) under a plugin-scoped capability; the extension
10
+ caches the descriptors in its registry and invokes them over same-origin
11
+ `window` `CustomEvent`s. This plugin answers registry-list requests, validates
12
+ invoke requests, resolves the active space, executes the target operation, and
13
+ surfaces outcomes as toasts. It also contributes three picker actions
14
+ (Person, Organization, Note) whose operations map a page snapshot to an ECHO
15
+ object, and owns the user-facing settings surface — including the render-proxy
16
+ settings (absorbed from the retired plugin-extension) for the extension's
17
+ page-rendering facility.
18
+
19
+ The extension owns the DOM picker, popup toolbar, extractors, and bridge
20
+ sender; this plugin owns envelope validation, snapshot → ECHO object mapping,
21
+ and user feedback (toast). The extension keeps a hand-validated mirror of the
22
+ wire types at `packages/apps/composer-crx/src/page-actions/types.ts` which
23
+ must be updated in lockstep.
24
+
25
+ ## Extensions
26
+
27
+ The following extension dialects are used in this document.
28
+ Each extension is defined in the Appendix or resolved via its URI.
29
+
30
+ | Term | URI |
31
+ |--------|--------------------------------|
32
+ | `type` | `org.dxos.mdl.type@1.0` |
33
+ | `feat` | `org.dxos.mdl.feat@1.0` |
34
+ | `scenario` | `org.dxos.mdl.scenario@1.0` |
35
+ | `test` | `org.dxos.mdl.test@1.0` |
36
+
37
+ ## Types
38
+
39
+ ```mdl
40
+ type Settings
41
+ desc: |
42
+ User preferences for extension ↔ Composer coordination, persisted via the
43
+ app-framework settings store. Only user-set values are stored; defaults
44
+ are applied by consumers when reading the atom. All fields are optional.
45
+ The fields are a configuration surface; enforcement is owned by whichever
46
+ consumer reads the atom.
47
+ fields:
48
+ enabled?: boolean # default: true. "Accept extension actions" preference.
49
+ autoOpenAfterClip?: boolean # default: false. "Open after action" preference.
50
+ renderProxyEnabled?: boolean # default: true. "Use extension to render pages" preference.
51
+ renderTimeout?: number # default: 20000. Max ms to wait for a render.
52
+ renderActiveTab?: boolean # default: false. Render in a focused tab.
53
+ developerMode?: boolean # default: false. Verbose logging / previews.
54
+ ```
55
+
56
+ ```mdl
57
+ type Source
58
+ fields:
59
+ url: string
60
+ title: string
61
+ favicon?: string
62
+ clippedAt: string # ISO timestamp
63
+ ```
64
+
65
+ ```mdl
66
+ type Selection
67
+ fields:
68
+ text: string # innerText of the picked subtree
69
+ html?: string # outerHTML, possibly truncated
70
+ htmlTruncated?: boolean
71
+ rect?: { x: number, y: number, width: number, height: number }
72
+ ```
73
+
74
+ ```mdl
75
+ type Hints
76
+ desc: |
77
+ Cheap signals harvested from the whole page at capture time. The mappings
78
+ use a subset (h1, og:title, og:description, og:image) as fallbacks; a
79
+ future agent stage may interpret all of them.
80
+ fields:
81
+ ogTitle?: string
82
+ ogDescription?: string
83
+ ogImage?: string
84
+ jsonLd?: unknown[]
85
+ h1?: string
86
+ firstImage?: string
87
+ ```
88
+
89
+ ```mdl
90
+ type Snapshot
91
+ desc: |
92
+ Generic page capture produced by the extension's `snapshot` extractor.
93
+ Travels as the `inputs` of an invoke request.
94
+ fields:
95
+ source: Source
96
+ selection?: Selection
97
+ hints?: Hints
98
+ imageData?: string # downscaled primary-image thumbnail (data URL),
99
+ # captured by the extension's background worker
100
+ html?: string
101
+ htmlTruncated?: boolean
102
+ ```
103
+
104
+ ```mdl
105
+ type PageActionDescriptor
106
+ desc: |
107
+ Serializable descriptor for a plugin-contributed page action, synced to
108
+ the extension's registry cache. Invocation is correlated by `id`.
109
+ fields:
110
+ id: string # stable plugin-scoped action id
111
+ label: string # human-readable action name
112
+ icon: string # phosphor icon identifier
113
+ urlPatterns: string[] # match patterns the action applies to
114
+ predicate?: { exists: string } # lazy DOM condition (CSS selector)
115
+ extractor: { name: string, params?: unknown } # extension-bundled extractor
116
+ contexts: string[] # surfaces: popup | page | selection | link | picker
117
+ operation: string # operation key, display/diagnostics only
118
+ ```
119
+
120
+ ```mdl
121
+ type PageAction
122
+ desc: |
123
+ A live page-action contribution: the descriptor fields plus the actual
124
+ operation definition (serialized to its key string when listed). The
125
+ target operation must accept `{ snapshot: Snapshot, target: Database }`
126
+ and return `{ id: string }`.
127
+ ```
128
+
129
+ ```mdl
130
+ type ListRequest
131
+ desc: |
132
+ Sent by the extension as `composer:page-actions:list`; acked as
133
+ `composer:page-actions:list:ack`.
134
+ fields:
135
+ version: number # runtime check: only 1 is supported
136
+ id: string # correlation id, echoed in the ack
137
+ ```
138
+
139
+ ```mdl
140
+ type ListAck
141
+ fields:
142
+ version: number # always 1
143
+ id: string
144
+ ok: boolean
145
+ actions?: PageActionDescriptor[] # when ok = true
146
+ error?: string # stable error code when ok = false
147
+ ```
148
+
149
+ ```mdl
150
+ type InvokeRequest
151
+ desc: |
152
+ Sent by the extension as `composer:page-action:invoke`; acked as
153
+ `composer:page-action:invoke:ack`. Decoded in two passes: a loose
154
+ envelope ({ version, id? }) first, so newer versions yield
155
+ `unsupportedVersion` rather than the generic `invalidPayload` and
156
+ malformed payloads can still echo the request id.
157
+ fields:
158
+ version: number # runtime check: only 1 is supported
159
+ id: string # correlation id, echoed in the ack
160
+ actionId: string # id of the registered action to invoke
161
+ page: { url: string, title: string, favicon?: string }
162
+ inputs: unknown # extractor output; `snapshot` yields Snapshot
163
+ invokedFrom: popup | contextMenu | picker
164
+ ```
165
+
166
+ ```mdl
167
+ type InvokeAck
168
+ fields:
169
+ version: number # always 1
170
+ id: string
171
+ ok: boolean
172
+ objectId?: string # ID of the created object when ok = true
173
+ error?: string # stable error code when ok = false:
174
+ # invalidPayload | unsupportedVersion |
175
+ # unknownAction | noSpace | operationFailed
176
+ ```
177
+
178
+ ## Features
179
+
180
+ ```mdl
181
+ feat F-1: Settings Capability
182
+
183
+ req F-1.1:
184
+ when: plugin is activated
185
+ then: |
186
+ a single `Settings` atom is registered under a plugin-scoped capability
187
+ and contributed to `AppCapabilities.Settings` so the standard settings
188
+ UI surfaces it.
189
+
190
+ req F-1.2:
191
+ when: another plugin reads the capability
192
+ then: it receives the current Settings with defaults applied
193
+ ```
194
+
195
+ ```mdl
196
+ feat F-2: Settings Surface
197
+
198
+ req F-2.1:
199
+ when: the app-framework settings panel renders the CRX section
200
+ then: |
201
+ a schema-driven form is shown covering all Settings fields, wired to
202
+ the Settings atom and persisted on change.
203
+
204
+ req F-2.2:
205
+ when: the user presses the Test connection button
206
+ then: |
207
+ a ping round-trips over the page → content-script → background path and
208
+ reports the extension's name and version on success, or the failure
209
+ reason otherwise.
210
+ ```
211
+
212
+ ```mdl
213
+ feat F-2c: Settings Enforcement
214
+
215
+ req F-2c.1:
216
+ when: `enabled` is false and a page-action invoke arrives
217
+ then: |
218
+ no object is created; the request is still acked (so the extension does
219
+ not time out) with the stable error code `disabled`
220
+
221
+ req F-2c.2:
222
+ when: `autoOpenAfterClip` is true and a page action succeeds
223
+ then: the created object is opened via LayoutOperation.Open
224
+
225
+ req F-2c.3:
226
+ when: `autoOpenAfterClip` is false (default)
227
+ then: focus is not moved; only the success toast is shown
228
+ ```
229
+
230
+ ```mdl
231
+ feat F-3: Page Action Contributions
232
+ desc: |
233
+ Plugins contribute `PageAction[]` under the plugin-scoped
234
+ `CrxCapabilities.PageAction` capability. Other plugins contribute their
235
+ own actions the same way (e.g. plugin-bookmarks contributes
236
+ "Add bookmark").
237
+
238
+ req F-3.1:
239
+ when: plugin is activated
240
+ then: |
241
+ three picker-only actions are contributed — Person, Organization, and
242
+ Note — each matching `http(s)://*/*`, using the extension-bundled
243
+ `snapshot` extractor, and targeting the corresponding
244
+ AddPersonFromSnapshot / AddOrganizationFromSnapshot /
245
+ AddNoteFromSnapshot operation.
246
+
247
+ req F-3.2:
248
+ when: the extension lists actions
249
+ then: |
250
+ contributions are resolved per-request, so actions from
251
+ late-activating plugins are included.
252
+ ```
253
+
254
+ ```mdl
255
+ feat F-4: Registry Listing
256
+
257
+ req F-4.1:
258
+ when: plugin is activated
259
+ then: |
260
+ window event listeners for `composer:page-actions:list` and
261
+ `composer:page-action:invoke` are installed, and a
262
+ `composer:page-actions:ready` event is dispatched so the extension
263
+ relay can refresh its registry even when Composer boots after the
264
+ content script has already installed.
265
+
266
+ req F-4.2:
267
+ when: a valid list request arrives
268
+ then: |
269
+ the ack carries the currently contributed actions as serializable
270
+ descriptors, with `operation` serialized to the operation's key string.
271
+
272
+ req F-4.3:
273
+ when: a list request fails schema decoding
274
+ then: |
275
+ the ack is { ok: false, error: 'invalidPayload' } with a best-effort
276
+ echo of the request id so the extension can correlate the failure.
277
+ ```
278
+
279
+ ```mdl
280
+ feat F-5: Invoke Validation & Dispatch
281
+
282
+ req F-5.1:
283
+ when: an invoke request's envelope fails decoding
284
+ then: ack is { ok: false, error: 'invalidPayload' } and nothing is invoked
285
+
286
+ req F-5.2:
287
+ when: the envelope has a version other than 1
288
+ then: |
289
+ ack is { ok: false, error: 'unsupportedVersion' } with the request id
290
+ echoed when present.
291
+
292
+ req F-5.3:
293
+ when: the strict payload fails decoding
294
+ then: ack is { ok: false, error: 'invalidPayload' }
295
+
296
+ req F-5.4:
297
+ when: `actionId` matches no registered action
298
+ then: ack is { ok: false, error: 'unknownAction' }
299
+
300
+ req F-5.5:
301
+ when: the active space cannot be determined
302
+ then: nothing is invoked; ack is { ok: false, error: 'noSpace' }
303
+
304
+ req F-5.6:
305
+ when: the request resolves to an action and an active space
306
+ then: |
307
+ the action's operation is invoked with
308
+ { snapshot: inputs, target: activeSpace.db }; on success the ack is
309
+ { ok: true, objectId } carrying the created object's id.
310
+
311
+ req F-5.7:
312
+ when: the operation returns an error or throws
313
+ then: ack is { ok: false, error: 'operationFailed' }
314
+ ```
315
+
316
+ ```mdl
317
+ feat F-6: Snapshot → Object Mapping
318
+ desc: |
319
+ Each contributed operation maps a Snapshot to an ECHO object and adds it
320
+ to the target database via SpaceOperation.AddObject.
321
+
322
+ req F-6.1:
323
+ when: AddPersonFromSnapshot is invoked
324
+ then: |
325
+ a Person is created with best-effort fields:
326
+ fullName ← hints.h1 ?? hints.ogTitle ?? firstLine(selection.text)
327
+ image ← hints.ogImage
328
+ notes ← selection.text (truncated to 4000 chars)
329
+ urls ← [{ label: source.title || source.url, value: source.url }]
330
+
331
+ req F-6.2:
332
+ when: AddOrganizationFromSnapshot is invoked
333
+ then: |
334
+ an Organization is created with best-effort fields:
335
+ name ← hints.ogTitle ?? hints.h1 ?? firstLine(selection.text)
336
+ description ← hints.ogDescription ?? selection.text (truncated)
337
+ image ← hints.ogImage
338
+ website ← source.url
339
+
340
+ req F-6.3:
341
+ when: AddNoteFromSnapshot is invoked
342
+ then: |
343
+ a Markdown document is created with a small prelude (title, source
344
+ link, timestamp) followed by the selection's rendered text (truncated
345
+ to 50000 chars). The title falls back through
346
+ hints.h1 ?? hints.ogTitle ?? firstLine(selection.text) ?? source.title.
347
+
348
+ req F-6.4:
349
+ when: there is no name/title candidate
350
+ then: the object is still created; empty name is acceptable
351
+ ```
352
+
353
+ ```mdl
354
+ feat F-7: User Feedback
355
+
356
+ req F-7.1:
357
+ when: an invoke ack is produced with ok = true
358
+ then: a success toast is shown
359
+
360
+ req F-7.2:
361
+ when: an invoke ack is produced with ok = false
362
+ then: an error toast is shown naming the stable error code
363
+ ```
364
+
365
+ ```mdl
366
+ feat F-8: Page Actions Registry & Invocation
367
+ desc: |
368
+ Plugins contribute page actions (serializable descriptor + target
369
+ operation) under a plugin-scoped capability. This plugin answers the
370
+ extension's registry-list requests (`composer:page-actions:list`) with
371
+ descriptors and executes invoke requests (`composer:page-action:invoke`)
372
+ by resolving the active space and invoking the target operation with
373
+ `{ snapshot, target }`. Every request is acked with stable error codes
374
+ (invalidPayload, unsupportedVersion, disabled, unknownAction, noSpace,
375
+ operationFailed); outcomes surface as toasts.
376
+ ```
377
+
378
+ ## Acceptance
379
+
380
+ ```mdl
381
+ scenario T-1: Settings atom registered
382
+ given: plugin activated
383
+ then:
384
+ - the Settings capability is available via Capability.get
385
+ - the default atom value is { enabled: true, autoOpenAfterClip: false,
386
+ renderProxyEnabled: true, renderTimeout: 20000, renderActiveTab: false,
387
+ developerMode: false }
388
+ ```
389
+
390
+ ```mdl
391
+ scenario T-2: Toggling enabled persists
392
+ given: settings surface rendered
393
+ when: user toggles the `enabled` switch off
394
+ then: subsequent reads of the atom observe enabled === false
395
+ ```
396
+
397
+ ```mdl
398
+ scenario T-3: List request returns descriptors
399
+ given: contributed picker actions (Person, Organization, Note)
400
+ when: handleListEvent receives { version: 1, id: 'r1' }
401
+ then:
402
+ - ack is { ok: true, id: 'r1' }
403
+ - ack.actions contains one descriptor per action with `operation`
404
+ serialized to the operation key string
405
+ ```
406
+
407
+ ```mdl
408
+ scenario T-4: Invalid list request is rejected
409
+ given: page-actions listeners installed
410
+ when: handleListEvent receives a malformed detail (missing id)
411
+ then: ack is { ok: false, error: 'invalidPayload' }
412
+ ```
413
+
414
+ ```mdl
415
+ scenario T-5: Valid person invoke creates Person
416
+ given: a registered add-person action and an active space
417
+ when: |
418
+ handleInvokeEvent receives { version: 1, actionId: <add-person>,
419
+ inputs: snapshot } with snapshot.hints.h1 = 'Jane Doe'
420
+ then:
421
+ - a Person is created with fullName = 'Jane Doe'
422
+ - ack is { ok: true, objectId: <non-empty> }
423
+ - a success toast is shown
424
+ ```
425
+
426
+ ```mdl
427
+ scenario T-6: Organization mapping falls back to og:title
428
+ given: a snapshot with hints.ogTitle = 'DXOS' and hints.h1 undefined
429
+ when: AddOrganizationFromSnapshot is invoked
430
+ then:
431
+ - Organization.name === 'DXOS'
432
+ - Organization.website === source.url
433
+ ```
434
+
435
+ ```mdl
436
+ scenario T-7: Missing name is allowed
437
+ given: a snapshot with no hints and empty selection text
438
+ when: the add-person action is invoked
439
+ then:
440
+ - a Person is created (empty fullName)
441
+ - ack is { ok: true, objectId: <non-empty> }
442
+ ```
443
+
444
+ ```mdl
445
+ scenario T-8: Unsupported envelope version
446
+ given: page-actions listeners installed
447
+ when: handleInvokeEvent receives { version: 2, id: 'r2', ... }
448
+ then:
449
+ - nothing is invoked
450
+ - ack is { ok: false, id: 'r2', error: 'unsupportedVersion' }
451
+ ```
452
+
453
+ ```mdl
454
+ scenario T-9: Invalid invoke payload is rejected
455
+ given: page-actions listeners installed
456
+ when: handleInvokeEvent receives a version-1 detail missing `actionId`
457
+ then:
458
+ - nothing is invoked
459
+ - ack is { ok: false, error: 'invalidPayload' } with the envelope id echoed
460
+ ```
461
+
462
+ ```mdl
463
+ scenario T-10: Unknown action
464
+ given: no action registered with id 'nope'
465
+ when: handleInvokeEvent receives a valid request with actionId = 'nope'
466
+ then: ack is { ok: false, error: 'unknownAction' }
467
+ ```
468
+
469
+ ```mdl
470
+ scenario T-11: No active space
471
+ given: the active space cannot be resolved
472
+ when: a valid invoke request arrives
473
+ then:
474
+ - nothing is invoked
475
+ - ack is { ok: false, error: 'noSpace' }
476
+ ```
477
+
478
+ ```mdl
479
+ scenario T-12: Operation failure
480
+ given: the target operation returns an error (or throws)
481
+ when: a valid invoke request arrives
482
+ then:
483
+ - ack is { ok: false, error: 'operationFailed' }
484
+ - an error toast is shown
485
+ ```
486
+
487
+ ---
488
+
489
+ ## QA
490
+
491
+ Executable tests. Each step carries a human instruction (`do`) and, where the step maps to an
492
+ invocable operation, an agent binding (`invoke`). Report failures by test and step number.
493
+
494
+ Note: this document declares no `op` blocks, so the operations below are referenced by their full
495
+ runtime key rather than `[op:...]` — see `src/types/CrxOperation.ts`.
496
+
497
+ ```mdl
498
+ test QA-1: Add a Person from a page snapshot
499
+ desc: |
500
+ The core snapshot → object mapping (F-6.1), standing in for the browser extension's picker,
501
+ which an agent cannot drive — the extension is a separate, unautomatable browser process. This
502
+ test invokes the target operation directly with a synthetic snapshot, exactly as the plugin's
503
+ own invoke handler does after resolving `{ snapshot, target }` (F-5.6).
504
+ actors: agent
505
+ covers: [F-3.1, F-5.6, F-6.1, T-5]
506
+ status: unverified
507
+ given:
508
+ - an identity exists
509
+ - space: a space is open
510
+ - plugin-crx is enabled — it is OFF by default (not in `getDefaults` and not in the dev-only
511
+ default set; see `packages/apps/composer-app/src/plugin-defs.tsx`)
512
+ - no Person named "QA Snapshot Person" exists — ABORT rather than reusing one this test did
513
+ not create
514
+ steps:
515
+ - name: Add the person from a snapshot
516
+ do: |
517
+ (Real path, not automatable here: with the extension installed, open the popup on a page
518
+ and choose the "Person" picker action, then select the page region to capture.)
519
+ invoke: |
520
+ org.dxos.operation.crx.addPersonFromSnapshot {
521
+ snapshot: { source: { url: "https://example.com/qa", title: "QA Page", clippedAt: "2026-08-27T00:00:00.000Z" },
522
+ hints: { h1: "QA Snapshot Person" } },
523
+ target: $given.space.db
524
+ }
525
+ capture: created
526
+ expect: a Person named "QA Snapshot Person" now exists in the space
527
+ assert: return !!$created.id
528
+ after:
529
+ - name: Delete the Person
530
+ do: Right-click "QA Snapshot Person" in the navtree and choose Delete.
531
+ invoke: org.dxos.operation.space.removeObjects { objects: [{ "/": "echo://$given.space.id/" + $created.id }], target: $given.space }
532
+ expect: the row leaves the navtree
533
+ note: |
534
+ `addPersonFromSnapshot` returns a bare `id`, not a live ref, so the DXN envelope is built
535
+ by hand for the removal call — see Rule 4 in `op@1.1`'s parent doc about resolving by the
536
+ invoker path rather than guessing a shortcut.
537
+ ```
538
+
539
+ ```mdl
540
+ test QA-2: Accept-extension-actions toggle gates invocation
541
+ desc: |
542
+ The settings enforcement path (F-2c.1) — no operation exists for the settings atom itself
543
+ (it is a plain app-framework settings store), so this step is human-only.
544
+ actors: human
545
+ covers: [F-1.1, F-2.1, F-2c.1]
546
+ status: unverified
547
+ given:
548
+ - an identity exists
549
+ - plugin-crx is enabled — it is OFF by default (see QA-1)
550
+ - the Settings panel's CRX section is reachable
551
+ steps:
552
+ - name: Disable "Accept extension actions"
553
+ do: Open Settings, find the CRX section, and toggle "Accept extension actions" off.
554
+ expect: the toggle shows off and the preference persists across a reload
555
+ note: |
556
+ NO `invoke` — the Settings atom has no operation binding; per F-2c.1, an invoke request
557
+ that arrives while this is off is still acked (error `disabled`), which requires the
558
+ extension itself to exercise and cannot be driven from the debug port alone.
559
+ after:
560
+ - name: Re-enable "Accept extension actions"
561
+ do: Toggle "Accept extension actions" back on.
562
+ expect: the toggle shows on again
563
+ ```
564
+
565
+ ---
566
+
567
+ ## Appendix: Extension Definitions
568
+
569
+ ```mdl
570
+ ext type
571
+ uri: org.dxos.mdl.type@1.0
572
+ desc: A named data structure with typed fields and optional literals.
573
+ fields:
574
+ desc?: Prose
575
+ fields?: FieldMap
576
+ literals?: UnionList
577
+ extends?: TypeRef[]
578
+ ```
579
+
580
+ ```mdl
581
+ ext feat
582
+ uri: org.dxos.mdl.feat@1.0
583
+ desc: A named feature grouping one or more requirements.
584
+ fields:
585
+ desc?: Prose
586
+ req: RequirementList
587
+ nesting: self
588
+ ```
589
+
590
+ ```mdl
591
+ ext scenario
592
+ uri: org.dxos.mdl.scenario@1.0
593
+ desc: An acceptance scenario expressed as given / when / then steps.
594
+ fields:
595
+ given?: Step | Step[]
596
+ when?: Step | Step[]
597
+ then: Step | Step[]
598
+ tags?: TagList
599
+ ```