cedar-embeddable-editor 1.5.2 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,12 +1,47 @@
1
- # Cedar Embeddable Editor (CEE)
1
+ # CEDAR Embeddable Editor (CEE)
2
2
 
3
- The CEDAR Embeddable Editor is as a web component that implements the functionality of the CEDAR Metadata Editor.
3
+ The CEDAR Embeddable Editor (CEE) is a reusable Web Component for adding
4
+ structured, standards-based metadata authoring to web applications.
4
5
 
5
- It takes CEDAR JSON Schema templates as input, and produces CEDAR JSON-LD metadata.
6
+ CEE dynamically renders data-entry forms from machine-actionable CEDAR
7
+ templates and produces semantically rich metadata as JSON-LD. Templates define
8
+ the fields, constraints, controlled vocabularies, and repeatable structures in a
9
+ form, allowing the metadata-authoring experience to evolve independently of the
10
+ application that embeds it. CEE also supports ontology-backed value selection
11
+ and persistent identifiers from external authorities such as ORCID and ROR.
12
+
13
+ For the design rationale, architecture, and deployments in research platforms,
14
+ see [*Author Once, Publish Everywhere: Portable Metadata Authoring with the CEDAR
15
+ Embeddable Editor*](https://doi.org/10.5334/dsj-2026-002), published in the
16
+ *Data Science Journal* (2026).
17
+
18
+ This README covers building, testing and releasing the component. For embedding
19
+ it in an application, the CEDAR documentation site carries a fuller guide:
20
+ [CEDAR Embeddable Editor](https://metadatacenter.readthedocs.io/en/latest/cedar-embeddable-editor/intro/).
21
+
22
+ ## Browser support
23
+
24
+ CEE supports the browser targets of the Angular version each release is built
25
+ with. It requires native Custom Elements v1 and native Shadow DOM.
26
+
27
+ Automated compatibility tests run against current desktop Chromium, Firefox and
28
+ WebKit engines. Firefox ESR and the configured Edge, Safari and iOS versions are
29
+ compilation targets, but are not all exercised as separate browser products.
30
+
31
+ Internet Explorer, legacy EdgeHTML, and browsers or embedded web views without
32
+ native `window.customElements` and Shadow DOM support are not supported. CEE
33
+ does not polyfill its host page. Consumers choosing to support browsers outside
34
+ this contract must load and maintain their own Web Components polyfills before
35
+ loading CEE.
6
36
 
7
37
  ## Running as a standalone application
8
38
 
9
- You can run CEE as a standalone application. This is helpful for developers to see changes to the code reflected immediately in the application. To run CEE in the standalone mode (NOT as a Webcomponent), you will need the editor itself and the sample templates that the editor uses. These are stored in a [separate repo](https://github.com/metadatacenter/cedar-component-distribution).
39
+ You can run CEE as a standalone application. This is helpful for developers to
40
+ see changes to the code reflected immediately in the application. The standalone
41
+ app fetches a small template and instance from `src/assets/cee-demo` and assigns
42
+ them to `templateAndInstanceObject`, the same way any host supplies an artifact,
43
+ so it needs no separate template server and no `cedar-component-distribution`
44
+ checkout.
10
45
 
11
46
  Proceed with the following steps:
12
47
 
@@ -16,12 +51,11 @@ Clone this repository onto a local directory of your choice:
16
51
 
17
52
  ```shell
18
53
  git clone https://github.com/metadatacenter/cedar-embeddable-editor.git
19
- git clone https://github.com/metadatacenter/cedar-component-distribution.git
20
54
  ```
21
55
 
22
56
  ### Edit configuration
23
57
 
24
- 1. Open the file ```app/app.component.dev.ts``` in your favorite editor.
58
+ 1. Open the file ```cedar-embeddable-editor/src/app/app.component.dev.ts``` in your favorite editor.
25
59
  2. Edit configuration parameters based on your local environment (see section [Configuration](https://github.com/metadatacenter/cedar-embeddable-editor/tree/develop#configuration) for details).
26
60
 
27
61
  ### Build the project and start the server
@@ -36,37 +70,187 @@ cedar-embeddable-editor$ npm install
36
70
  cedar-embeddable-editor$ ng serve
37
71
  ```
38
72
 
39
- 1. In a different shell navigate to the component distribution directory:
73
+ 1. In your browser, navigate to `http://localhost:4400/`. The app will automatically reload if you change any of the source files.
74
+
75
+ ## Building the Web Component
76
+
77
+ CEE is shipped as one JavaScript file that can be embedded in an application or
78
+ HTML page. Do not concatenate named Angular output files manually: their names,
79
+ locations, and module structure change when Angular changes builders.
80
+
81
+ Build the production application, then run the browser suite against the
82
+ single-file bundle it produced:
83
+
40
84
  ```shell
41
- $ cd <...>/<clone directory>/cedar-component-distribution/
85
+ nvm use
86
+ npm run build:production
87
+ npm run test:visual:prebuilt
42
88
  ```
43
89
 
44
- 1. Run these commands:
90
+ One Node version throughout — 24.19.0, which `.nvmrc` names. The build and the
91
+ tests used to run on different ones, because Angular 14's toolchain and the
92
+ Playwright the suite needs did not accept the same version; from Angular 15 they
93
+ do, so the dist that ships is produced on the same Node that exercised it.
94
+
95
+ Once that exact bundle is green, stage the publishable npm directory from it:
96
+
45
97
  ```shell
46
- cedar-embeddable-editor$ npm install
47
- cedar-embeddable-editor$ ng serve
98
+ npm run package:npm:prebuilt
48
99
  ```
49
100
 
50
- 1. In your browser, navigate to `http://localhost:4400/`. The app will automatically reload if you change any of the source files.
101
+ This copies the tested bytes to
102
+ `dist-npm/cedar-embeddable-editor/cedar-embeddable-editor.js`, refreshes its
103
+ version, README, changelog, and package lock, and records the bundle manifest.
104
+ The command fails if the browser bundle is stale or does not match its SHA-256
105
+ digest. `npm run check:npm-package` can repeat the byte-for-byte verification
106
+ before `npm pack` or `npm publish`.
107
+
108
+ ## Running as an `npm` package
109
+
110
+ Releases are published to npmjs.org as
111
+ [`cedar-embeddable-editor`](https://www.npmjs.com/package/cedar-embeddable-editor)
112
+ under the `latest` tag, so an embedder installs the current one by name:
113
+
114
+ ```shell
115
+ npm install cedar-embeddable-editor
116
+ ```
51
117
 
52
- ## Building the Webcomponent
118
+ `1.6.0` is current on npmjs.org.
53
119
 
54
- This method creates a single Javascript (JS) file that encapsulates all the functionality of CEE. The JS file can be embedded in any application or HTML page. To build a CEE Webcomponent, proceed with these steps:
120
+ Dev snapshots go somewhere else: the BMIR Nexus, as the scoped
121
+ `@org.metadatacenter/cedar-embeddable-editor` under a `dev` tag. `scripts/npm-package.mjs`
122
+ derives which from the version — a `-dev.` in it selects the scoped name and the Nexus
123
+ registry, and anything else the unscoped name and the default one — so the two channels
124
+ cannot be confused by a flag someone forgets to pass. npm routes by scope rather than by
125
+ package name, which is what lets one package come from Nexus while everything else resolves
126
+ from npmjs.org.
55
127
 
56
- ### Build and copy the Webcomponent JS file
128
+ A CEDAR frontend names a snapshot through an npm alias:
129
+
130
+ ```json
131
+ "cedar-embeddable-editor": "npm:@org.metadatacenter/cedar-embeddable-editor@2.0.0-dev.20260816.5e7dca6"
132
+ ```
133
+
134
+ Cutting one is in
135
+ [CEE-RUNBOOK.md](https://github.com/metadatacenter/cedar-development/blob/develop/ops/CEE-RUNBOOK.md),
136
+ including the version convention and which host needs what afterwards. Publishing needs the
137
+ Nexus credential; reads are anonymous.
138
+
139
+ ## Testing
140
+
141
+ The complete test gate is available from the repository root:
57
142
 
58
- 1. Run the build command:
59
143
  ```shell
60
- cedar-embeddable-editor$ ng build --configuration=production
144
+ npm run test:ci
61
145
  ```
62
- 1. Combine the generated files into a single file and copy the final JS to the sample application:
146
+
147
+ It runs, in order:
148
+
149
+ 1. `ng lint` over the sources and the ESLint configuration.
150
+ 2. A type check of the application and the domain harness, with `strict` on
151
+ throughout.
152
+ 3. The unit tests, in Node under Vitest.
153
+ 4. The headless domain harness with V8 coverage, and its per-directory coverage
154
+ floors.
155
+ 5. A production build, then the Playwright suite against that bundle, in a
156
+ container: the full Chromium baseline at desktop and narrow viewport sizes,
157
+ plus focused Chromium, Firefox and WebKit compatibility checks. The container
158
+ is what makes a screenshot baseline mean the same thing on a laptop and on CI,
159
+ so the pixel budget is zero — see `visual/run-in-container.sh`.
160
+ 6. Staging the npm package from the bundle the suite just exercised, which
161
+ checks the raw and gzip size budgets and verifies every staged byte against
162
+ its source.
163
+
164
+ The domain corpora are checked into `harness/fixtures/`; running the tests does
165
+ not require `cedar-artifact-library` or `cedar-test-artifacts` checkouts.
166
+
167
+ `.github/workflows/test.yml` runs the same gate on every pull request and on
168
+ pushes to `main`, `develop` and the `cee-angular-**` branches. Nothing is
169
+ published from CI: releasing is a separate, manual procedure.
170
+
171
+ ### Auditing what ships
172
+
63
173
  ```shell
64
- cedar-embeddable-editor$ cat dist/cedar-embeddable-editor/{runtime,polyfills,main}.js > cedar-embeddable-editor.js
174
+ npm run audit:prod
65
175
  ```
66
176
 
67
- ## Running as an `npm` package
177
+ Only runtime dependencies reach the file an embedder downloads, so this audit is
178
+ the one that describes the shipped artifact, and it is deliberately not part of
179
+ `test:ci` — it can fail on a disclosure rather than on a commit, which would
180
+ break an unrelated pull request its author cannot fix.
181
+
182
+ A root `npm audit` reports advisories against `@angular/cli` and the packages
183
+ reached through it. **Never run `npm audit fix --force` here.** npm's idea of
184
+ fixing that tree is to walk the toolchain years backwards, undoing the Angular
185
+ march to silence warnings about build tooling an embedder never downloads.
186
+
187
+ ### First-time setup
188
+
189
+ CEE resolves the model library from `@org.metadatacenter/cedar-model-typescript-library`,
190
+ published to the BMIR Nexus, so no sibling checkout is needed:
191
+
192
+ ```shell
193
+ npm ci
194
+ npm --prefix harness ci
195
+ ```
196
+
197
+ The visual suite installs nothing here. It runs inside Playwright's own container,
198
+ which carries the browsers it drives, and installs its dependencies there against a
199
+ named volume — so it needs Docker running and no `playwright install` of its own.
200
+
201
+ ### Node versions during the Angular migration
202
+
203
+ The root `.nvmrc` pins the Node version used to update, lint and compile the
204
+ current Angular version. Move that pin with each completed framework hop.
205
+
206
+ CEE is on **Angular 22.1** and **Node 24.19.0**, named in `.nvmrc`, declared in
207
+ `engines`, and pinned by CI. Angular 22 accepts `^22.22.3 || ^24.15.0 || >=26`; 24
208
+ is the active LTS where 22 is in maintenance, so that is the one CEE uses.
209
+
210
+ Build and test share it. Through Angular 14 they could not: no Node version
211
+ satisfied both that toolchain and the test tools, so CI built on one and switched
212
+ the runner to another without replacing `dist`. `npm run test:ci:prebuilt` is what
213
+ remains of that arrangement — it tests an already-built artifact and deliberately
214
+ does not invoke `ng build`, which is still what CI wants, because it means the
215
+ bytes tested are the bytes that ship.
216
+
217
+ Only that one package comes from Nexus; everything else resolves from npmjs.org.
218
+ An `.npmrc` alongside each of the three `package.json` files maps the
219
+ `@org.metadatacenter` scope to Nexus, and reads need no credentials.
220
+
221
+ Each manifest depends on it under an alias:
222
+
223
+ ```json
224
+ "cedar-model-typescript-library": "npm:@org.metadatacenter/cedar-model-typescript-library@<version>"
225
+ ```
226
+
227
+ The alias keeps the local import name, so source files import
228
+ `cedar-model-typescript-library` regardless of the published name. To move to a
229
+ newer build, publish it to Nexus and bump the version in the root and `visual/`
230
+ manifests together. The harness declares no separate copy; it resolves the root
231
+ installation.
232
+
233
+ ### Focused test commands
234
+
235
+ Use these when working on one layer:
236
+
237
+ ```shell
238
+ npm run test:unit:ci # Vitest unit tests, one run
239
+ npm run test:unit:coverage # unit tests with coverage report
240
+ npm run test:domain # Vitest domain harness
241
+ npm run test:domain:coverage # domain harness with coverage report
242
+ npm run test:bundle-size # exact raw and gzip budgets for the shipped bundle
243
+ npm run test:visual # production build, fixture preparation, Playwright
244
+ ```
245
+
246
+ `npm test` runs the unit tests once, and `npm run test:watch` keeps them running
247
+ for interactive development. Use `npm run test:ci` for a complete verification.
68
248
 
69
- Please import the latest version of the editor into your project from: [https://www.npmjs.com/package/cedar-embeddable-editor](https://www.npmjs.com/package/cedar-embeddable-editor)
249
+ The unit tests run in Node rather than a browser. None of them uses `TestBed`, so
250
+ none needs Angular's JIT compiler to build a component, and dropping the browser
251
+ took the suite from a Chrome launch to about a second. Anything that does need a
252
+ real browser belongs in the Playwright suite under `visual/`, which tests the
253
+ shipped bundle rather than the sources.
70
254
 
71
255
  ## Configuration
72
256
 
@@ -75,11 +259,11 @@ Please import the latest version of the editor into your project from: [https://
75
259
  The CEE configuration file format and storage location depends on the application and the mode in which CEE is being used.
76
260
 
77
261
  * When running CEE in the standalone mode (developer mode), the configuration parameters are stored in and read from the file: `src/app/app.component.dev.ts`.
78
- * When running CEE as a generic Webcomponent, the configuration parameters can be stored in any `.json` file that is visible to the application that embeds CEE Webcomponent. CEE Webcomponent API provides a method for loading the configuration file from its path at runtime. For example:
262
+ * When running CEE as a generic Webcomponent, the configuration parameters can be stored in any `.json` file that is visible to the application that embeds CEE Webcomponent. Fetch it and assign the result:
79
263
  ```javascript
80
- document.addEventListener('WebComponentsReady', function () {
264
+ customElements.whenDefined('cedar-embeddable-editor').then(async () => {
81
265
  const cee = document.querySelector('cedar-embeddable-editor');
82
- cee.loadConfigFromURL('assets/data/cee-config.json');
266
+ cee.config = await (await fetch('assets/data/cee-config.json')).json();
83
267
  });
84
268
  ```
85
269
  * The configuration can also be passed into the editor as a json map. In Angular this looks as follows:
@@ -94,63 +278,224 @@ document.addEventListener('WebComponentsReady', function () {
94
278
 
95
279
  ### Required configuration parameters
96
280
 
97
- * **showSampleTemplateLinks:** Wether the sample links are shown or not.
98
- * For production this should be false, the template should be injected into the component by the embedding application
99
- * **terminologyIntegratedSearchUrl:** The URL of the CEDAR integrated search endpoint that communicates with BioPortal.
100
- * The value `https://terminology.metadatacenter.org/bioportal/integrated-search` should work for the majority of applications.
281
+ Two keys name the CEDAR services CEE calls, and neither has a default. CEE cannot
282
+ know which deployment it is embedded in, and a default would name one so a key
283
+ left unset turns its lookups off and CEE reports which key is missing, rather than
284
+ sending a host's users' keystrokes to somebody else's server.
285
+
286
+ Both are bases and both must end in a slash. Every path below them is CEE's own.
287
+
288
+ * **terminologyBaseUrl:** the CEDAR terminology server, which searches BioPortal.
289
+ Unset, controlled fields offer no terms.
290
+ * **bridgeBaseUrl:** the CEDAR bridge server, which reaches the external
291
+ authorities. Unset, the seven authority fields offer no terms and resolve no
292
+ identifiers.
101
293
 
102
294
  ```json
103
295
  {
104
- "showSampleTemplateLinks": false,
105
- "terminologyIntegratedSearchUrl": 'https://terminology.metadatacenter.org/bioportal/integrated-search',
296
+ "terminologyBaseUrl": "https://terminology.metadatacenter.org/",
297
+ "bridgeBaseUrl": "https://bridge.metadatacenter.org/"
106
298
  }
107
299
  ```
108
300
 
109
301
  ### Optional configuration parameters
110
302
 
111
- There are other optional configuration parameters available for controlling various aspects of the CEE user interface. Most of these are self-explanatory. The example below includes the default values in cases, where the parameter isn't explicitly declared.
303
+ Every other key is optional. The defaults below are the component's own, read
304
+ from `CedarEmbeddableMetadataEditorComponent` and its wrapper, not from the
305
+ standalone developer app in `src/app/app.component.dev.ts`, whose values differ.
306
+
307
+ What the user sees:
308
+
309
+ | Key | Default |
310
+ |---|---|
311
+ | `showTemplateDescription` | `false` |
312
+
313
+ CEE draws no page chrome of its own. It used to render a header carrying the CEDAR
314
+ logo and title, and a footer carrying the Stanford Division of Computational
315
+ Medicine mark and a contact link, behind `showHeader` and `showFooter`. Every string
316
+ and destination was hardcoded, so an embedder took CEDAR's branding or nothing.
317
+ A host renders its own header and footer around the element; the standalone app in
318
+ `src/app/app.component.dev.html` is a worked example.
319
+
320
+ What CEE keeps is the CEDAR mark and the version stamp inside the form's own title
321
+ block, which is a component naming itself rather than dressing someone else's page.
322
+
323
+ Editing behaviour and serialization:
324
+
325
+ | Key | Default |
326
+ |---|---|
327
+ | `readOnlyMode` | `false` |
328
+ | `trustTemplateRichText` | `false` |
329
+
330
+ `showDownloadMenu` offers a menu that saves CEE's views of the artifact as files.
331
+ It defaults to `false`, and nothing is rendered under the form either way:
332
+
333
+ | Menu entry | Saves | As |
334
+ |---|---|---|
335
+ | JSON-LD - Instance | The instance as a CEDAR document | `<name>-instance.json` |
336
+ | YAML - Instance | The same instance, as CEDAR YAML | `<name>-instance.yaml` |
337
+ | Compact YAML - Instance | The same instance without root identity and provenance metadata | `<name>-instance-compact.yaml` |
338
+ | JSON Schema - Template | The template as the host supplied it | `<name>-template.json` |
339
+ | YAML - Template | The same template, as CEDAR YAML | `<name>-template.yaml` |
340
+ | Compact YAML - Template | Its compact authoring form, without repository-managed metadata | `<name>-template-compact.yaml` |
341
+ | Data Quality Report | Required-field tally and constraint violations | `<name>-data-quality.json` |
342
+
343
+ `<name>` is the template's own `schema:name`, reduced to file-name-safe
344
+ characters, so a developer with several forms open can tell the files apart.
345
+
346
+ These were eight panels once, each printing a dump under the form, and each
347
+ costing two keys — one to show it and one to expand it. Two of the sixteen were
348
+ on by default, so an embedder who configured nothing got a JSON Schema dump and
349
+ a JSON-LD dump beneath every form.
350
+
351
+ A download is started by the page, which a host running under a restrictive
352
+ sandbox can refuse, with no event to observe when it does. CEE traces each
353
+ attempt through the event handler, so a developer seeing the trace and no file
354
+ knows to look at their own sandbox.
355
+
356
+ Language:
357
+
358
+ | Key | Default |
359
+ |---|---|
360
+ | `defaultLanguage` | `en` |
361
+ | `fallbackLanguage` | `en` |
362
+ | `languageMapPathPrefix` | none |
363
+
364
+ `trustTemplateRichText` decides whether a template author's rich text renders verbatim
365
+ or is sanitized first. It defaults to `false` and should stay there unless your
366
+ application controls which templates load — see [Embedding security](#embedding-security).
367
+
368
+ `bridgeBaseUrl` is the whole of the external-authority surface, covering the
369
+ seven authorities: ORCID, ROR, PFAS, PubMed, RRID, NIH Grant and DOI. CEE appends
370
+ the bridge server's `ext-auth/` resource, then the path for the authority a field
371
+ is bound to — a search path for a name typed into it, and a details path for an
372
+ identifier pasted into it. All of that is the bridge server's own route shape, so
373
+ none of it is configurable: a deployment moves all fourteen endpoints by moving
374
+ the base, or none of them.
375
+
376
+ | Authority | Search path | Details path |
377
+ |---|---|---|
378
+ | ORCID | `orcid/search-by-name` | `orcid` |
379
+ | ROR | `ror/search-by-name` | `ror` |
380
+ | PFAS | `comp-tox/search-by-name` | `comp-tox` |
381
+ | PubMed | `pmid/search-by-name` | `pmid` |
382
+ | RRID | `rrid/search-by-name` | `rrid` |
383
+ | NIH Grant | `nih-grant/search-by-name` | `nih-grant` |
384
+ | DOI | `doi/search-by-name` | `doi` |
385
+
386
+ ### TypeScript types
387
+
388
+ The package ships declarations. A host importing them gets a checked configuration
389
+ object and a typed element:
390
+
391
+ ```ts
392
+ import type { CeeConfig, CedarEmbeddableEditorElement } from 'cedar-embeddable-editor';
393
+
394
+ const config: CeeConfig = { readOnlyMode: true, showDownloadMenu: true };
395
+
396
+ // Typed by the package, with no cast: it declares the tag in HTMLElementTagNameMap.
397
+ const cee = document.querySelector('cedar-embeddable-editor');
398
+ cee!.config = config;
399
+ const report = cee!.dataQualityReport; // CeeDataQualityReport
400
+ ```
112
401
 
113
- ```json
114
- {
115
- "sampleTemplateLocationPrefix": "http://localhost:4240/cedar-embeddable-editor-sample-templates/",
116
- "loadSampleTemplateName": "01",
117
- "expandedSampleTemplateLinks": true,
118
- "showTemplateDescription": false,
402
+ The declarations are **types only**. The bundle is a script that registers a custom
403
+ element and exports no values, so there is nothing to import at runtime — use
404
+ `import type`, and let the interface rather than a constant catch a mistyped key.
405
+
406
+ If you are not using TypeScript, or your configuration comes from a JSON file no
407
+ compiler has seen, CEE checks it at runtime instead and reports what it cannot use.
408
+ An unknown key is named, with the nearest real key suggested; a value of the wrong
409
+ kind says what was expected; and settings that conflict are called out. The messages
410
+ go to the console and to any `eventHandler` you registered:
411
+
412
+ ```
413
+ CEE ERROR: Unknown configuration key "readOnlyMod". It has no effect. Did you mean "readOnlyMode"?
414
+ ```
415
+
416
+ A key CEE cannot use is reported *and* refused: it reads as unset, so the setting
417
+ keeps the default it documents. One bad key costs only that key — every other key in
418
+ the same configuration applies. CEE does not repair a value either, so a base URL
419
+ missing its trailing slash is dropped rather than completed, since appending CEE's
420
+ own path to it would name an endpoint nobody chose.
421
+
422
+ An assignment that is not an object at all configures nothing and does not spend the
423
+ one assignment there is, so your next attempt is still your first.
424
+
425
+ Configuration and the artifact inputs take one assignment each and keep it. Assign
426
+ `config` a second time, or an artifact input a second time, and CEE reports it and
427
+ ignores it: the first value stands. Build the configuration you want, assign it once,
428
+ and create a new element if it has to change. `eventHandler` is the exception and may
429
+ be replaced, with the last handler assigned receiving — register it before the
430
+ configuration and the artifact if you want the diagnostics from those, since a
431
+ handler hears only what follows it.
432
+
433
+ The same handler provides the lifecycle signal a host can use instead of polling
434
+ the DOM. `ready` is called once after the element's first successful form render;
435
+ it is not called for a rejected artifact and is not replayed to a handler attached
436
+ after rendering:
437
+
438
+ ```javascript
439
+ cee.eventHandler = {
440
+ error: (message) => console.error(message),
441
+ ready: () => startAutosave(),
442
+ };
443
+ ```
119
444
 
120
- "showTemplateRenderingRepresentation": true,
121
- "expandedTemplateRenderingRepresentation": false,
445
+ `readOnlyMode` is the only way in or out of read-only mode. CEE used to offer the
446
+ user a switch of its own, in a preferences menu, which wrote to the same state the
447
+ widgets read — so a form you embedded as a viewer could be made editable from inside
448
+ it. Both are gone, along with the `showPreferencesMenu` key that governed the menu.
122
449
 
123
- "showInstanceDataCore": true,
124
- "expandedInstanceDataCore": false,
450
+ ## Embedding security
125
451
 
126
- "showMultiInstanceInfo": true,
127
- "expandedMultiInstanceInfo": false,
452
+ CEE renders inside your page, in your origin. It is a custom element using Shadow
453
+ DOM, and **Shadow DOM is not a security boundary**: it scopes styles and markup, not
454
+ privileges. Anything CEE executes runs with the same access to cookies, storage and
455
+ network as the rest of your application.
128
456
 
129
- "showInstanceDataFull": false,
130
- "expandedInstanceDataFull": false,
457
+ That matters for one input in particular.
131
458
 
132
- "showTemplateSourceData": true,
133
- "expandedTemplateSourceData": false,
459
+ ### Templates are trusted input
134
460
 
135
- "showDataQualityReport": false,
136
- "expandedDataQualityReport": false,
461
+ A template can carry a **static rich-text field**, whose body is HTML composed by the
462
+ template's author and rendered as HTML by CEE. Instance data is different and is
463
+ always sanitized — a value a form's user typed can never introduce markup that runs.
464
+ The question is only what a *template author* may do.
137
465
 
138
- "showHeader": true,
139
- "showFooter": true,
466
+ CEE sanitizes template rich text by default. Script elements, event-handler
467
+ attributes such as `onerror`, `javascript:` URLs, `iframe`, `form` controls and
468
+ AngularJS directive attributes such as `ng-click` are removed. Formatting is
469
+ preserved: inline styles, tables, lists, headings, links, and inline `data:` images
470
+ in the raster formats all render as the author composed them.
140
471
 
141
- "languageMapPathPrefix": null,
142
- "defaultLanguage": "en",
143
- "fallbackLanguage": "en",
472
+ If your application decides which templates load — they ship with the application, or
473
+ come from a source you control — you may prefer the author's markup to render exactly
474
+ as written:
144
475
 
145
- "collapseStaticComponents": false,
146
- "showStaticText": true,
147
-
148
- "readOnlyMode": false,
149
- "hideEmptyFields": false,
150
- "showPreferencesMenu": true
476
+ ```json
477
+ {
478
+ "trustTemplateRichText": true
151
479
  }
152
480
  ```
153
- Enabling of hiding empty fields is only possible in read-only mode.
481
+
482
+ **Only set this if template authors are as trusted as your own application code.**
483
+ With it on, a template author can run JavaScript in your origin. "Allowed to define a
484
+ form" and "allowed to run code in this page" are very different permissions, and this
485
+ key is where you say they are the same for your deployment.
486
+
487
+ In particular, **do not set it if your users choose their own templates** — from
488
+ CEDAR's public library, or from anywhere your users can write to. Leave it off and
489
+ CEE will render the formatting without the risk.
490
+
491
+ ### What is sanitized where
492
+
493
+ | Content | Origin | Treatment |
494
+ |---|---|---|
495
+ | Static rich-text field body | Template author | Sanitized, unless `trustTemplateRichText` is on |
496
+ | Static section break, image, YouTube | Template author | Not rendered as HTML; content is used as text or a URL |
497
+ | Field values, in the form and in read-only view | Instance data | Always sanitized. Not configurable |
498
+
154
499
  ## Metadata API
155
500
 
156
501
  CEE Webcomponent includes APIs for exporting metadata externally and importing metadata into CEE.
@@ -163,12 +508,28 @@ The metadata currently being edited inside CEE can be exported at anytime by mak
163
508
  const meta = cee.currentMetadata;
164
509
  ```
165
510
 
511
+ `currentMetadata` always returns a CEDAR JSON object. For YAML, read the
512
+ companion accessor instead:
513
+
514
+ ```javascript
515
+ const yaml = cee.currentMetadataYaml; // always a YAML string
516
+ ```
517
+
518
+ Either accessor works whatever form the template arrived in. A template written
519
+ as CEDAR YAML is assigned to `templateObject` like any other, as the parsed YAML
520
+ object rather than the YAML source string, and CEE picks the reader from the
521
+ template's own shape:
522
+
523
+ ```javascript
524
+ cee.templateObject = parsedTemplateYaml;
525
+ ```
526
+
166
527
  In the example below, the metadata is sent to an external endpoint every 15 seconds:
167
528
 
168
529
  ```javascript
169
- document.addEventListener('WebComponentsReady', function () {
530
+ customElements.whenDefined('cedar-embeddable-editor').then(async () => {
170
531
  const cee = document.querySelector('cedar-embeddable-editor');
171
- cee.loadConfigFromURL('assets/data/cee-config.json');
532
+ cee.config = await (await fetch('assets/data/cee-config.json')).json();
172
533
  const saveTime = 15000; // 15 seconds
173
534
 
174
535
  setInterval(() => {
@@ -200,38 +561,32 @@ You can inject your metadata into CEE, provided it matches the template currentl
200
561
  cee.instanceObject = yourCustomMetadataJson
201
562
  ```
202
563
 
203
- In the example below, the metadata is fetched from a remote URL and injected into CEE:
564
+ `templateObject` and `instanceObject` are independent, and either may be assigned
565
+ first: CEE does not build the form until a template is present, so an instance
566
+ supplied ahead of one waits rather than loading against nothing.
204
567
 
205
- ```javascript
206
- function restoreMetadataFromURL(metaUrl, cee, successHandler = null, errorHandler = null) {
207
- const xhr = new XMLHttpRequest();
208
- xhr.onreadystatechange = () => {
209
- if (xhr.readyState === XMLHttpRequest.DONE) {
210
- if (xhr.status === 200) {
211
- const jsonMeta = JSON.parse(xhr.responseText);
212
- cee.instanceObject = jsonMeta;
213
-
214
- if (successHandler) {
215
- successHandler(jsonMeta);
216
- }
217
- } else {
218
- if (errorHandler) {
219
- errorHandler(xhr);
220
- }
221
- }
222
- }
223
- };
224
- xhr.open('GET', metaUrl, true);
225
- xhr.send();
226
- }
568
+ Each takes one assignment. Fetch the metadata before you assign it, rather than
569
+ assigning a placeholder and correcting it once the fetch lands:
227
570
 
228
- document.addEventListener('WebComponentsReady', function () {
571
+ ```javascript
572
+ customElements.whenDefined('cedar-embeddable-editor').then(async () => {
229
573
  const cee = document.querySelector('cedar-embeddable-editor');
230
- cee.loadConfigFromURL('assets/data/cee-config.json');
231
- restoreMetadataFromURL('uploads/metadata-for-restore.json', cee);
574
+ cee.config = await (await fetch('assets/data/cee-config.json')).json();
575
+ cee.instanceObject = await (await fetch('uploads/metadata-for-restore.json')).json();
576
+ cee.templateObject = yourCustomTemplateJson;
232
577
  });
233
578
  ```
234
579
 
580
+ An instance counts only after CEE can read it. If deserialization fails, CEE
581
+ reports the rejection to the console and to `eventHandler`, does not render a
582
+ replacement empty form, and leaves the instance assignment available for a
583
+ corrected value. The same rule applies to the instance inside
584
+ `templateAndInstanceObject`; a rejected combined value spends neither artifact
585
+ assignment.
586
+
587
+ To load a different instance, create a new element. Reassigning `instanceObject`
588
+ reports an error and leaves the first instance in place.
589
+
235
590
  To reiterate, the metadata being injected **MUST** match the template currently being edited and open in your browser window.
236
591
 
237
592
  ### Injecting Template And Metadata Together
@@ -246,6 +601,36 @@ cee.templateAndInstanceObject = templateAndInstance;
246
601
  Injecting template and metadata together brings performance benefits as well as allows configuring hiding empty fields.
247
602
  Object being injected must strictly have two objects one named 'templateObject' and the other 'instanceObject'.
248
603
 
604
+ ### Temporal Values
605
+
606
+ CEE treats a temporal field's declared `temporalType`, `temporalGranularity`
607
+ and `timezoneEnabled` settings as its storage contract. The editor shows only
608
+ the parts named by that contract and emits a complete lexical `xsd:date`,
609
+ `xsd:time` or `xsd:dateTime` value:
610
+
611
+ | Declared precision | Canonical stored example |
612
+ | --- | --- |
613
+ | date, year | `2026-01-01` |
614
+ | date, month | `2026-08-01` |
615
+ | date, day | `2026-08-09` |
616
+ | time, hour | `21:00:00` |
617
+ | time, minute | `21:45:00` |
618
+ | time, second | `21:45:32` |
619
+ | time, decimal second | `21:45:32.001` |
620
+ | date-time, day | `2026-08-09T00:00:00` |
621
+ | date-time, minute | `2026-08-09T21:45:00` |
622
+
623
+ The same padding rule applies to the other date-time granularities. If time
624
+ zones are enabled, CEE appends the selected fixed offset (`Z` or `+/-HH:mm`);
625
+ if they are disabled, any offset is removed.
626
+
627
+ Granularity is authoritative when an existing instance is loaded. Information
628
+ finer than the declared granularity is intentionally discarded and the
629
+ canonical value is written back. For example,
630
+ `2026-08-09T21:45:32.125-07:00` in a day-granularity date-time field becomes
631
+ `2026-08-09T00:00:00-07:00`. Embedders should account for that normalization
632
+ when comparing a saved instance with its original input.
633
+
249
634
  ### Data Quality Report
250
635
 
251
636
  The dataQualityReport summarizes basic metrics on the instance data.
@@ -254,14 +639,49 @@ The dataQualityReport summarizes basic metrics on the instance data.
254
639
  const report = cee.dataQualityReport;
255
640
  ```
256
641
 
257
- At the moment these three fields are available, with more to come:
642
+ The report answers two questions: is anything required missing, and is anything
643
+ present invalid.
258
644
 
259
645
  ```
260
646
  requiredFieldValueCount: int
261
647
  nonNullRequiredFieldValueCount: int
648
+ problems: ValidationProblem[]
262
649
  isValid: boolean
263
650
  ```
264
651
 
652
+ `isValid` is true when nothing required is missing **and** `problems` is empty.
653
+
654
+ Each problem names the field and what is wrong with it:
655
+
656
+ ```javascript
657
+ {
658
+ path: ['_author', '_email'], // component path from the template root
659
+ field: '_email',
660
+ inputType: 'email',
661
+ code: 'email', // stable, matchable without parsing the message
662
+ message: 'Not a valid email address.',
663
+ value: 'not-an-email'
664
+ }
665
+ ```
666
+
667
+ Constraints checked: `requiredValue`; `minLength`, `maxLength` and `regex`;
668
+ email, link, phone and external-authority IRI format; numeric type — including
669
+ `xsd:decimal`, `xsd:byte` and `xsd:short` — with the type's own range,
670
+ `minValue`, `maxValue` and `decimalPlace`; temporal shape against
671
+ `temporalType`, `granularity` and `timezoneEnabled`, plus calendar validity;
672
+ membership of a value in its declared choice literals; `minItems` and
673
+ `maxItems`; and the structure of a controlled-term value.
674
+
675
+ Controlled-term **membership** — whether a term belongs to the declared
676
+ ontologies, value sets, classes or branches — is not checked. It requires the
677
+ terminology server, and a local synchronous report should not depend on the
678
+ network. Structural checks on controlled values (`@id` and `rdfs:label` present
679
+ as a pair, `@id` well-formed) are performed.
680
+
681
+ An absent value produces no constraint problems; that is the required check's
682
+ business, so an empty form reports what is missing rather than also reporting
683
+ every blank as malformed.
684
+
265
685
  ### Language Maps / Translations
266
686
 
267
687
  The application currently has two built-in language maps: `en` and `hu`. If you do not specify any language-related config option, the default `English` map will be used.
@@ -301,27 +721,43 @@ Information about the loading process is logged onto the console with the `CEE T
301
721
 
302
722
  ### Listening for changes
303
723
 
304
- If you need to listen to data changes inside the embeddable editor, you can use the existing `change` DOM events. We added custom events in case of a multi-instance add, copy and delete operations, so you can listen to all the events on the instance.
724
+ CEE emits one composed, bubbling `change` event after an operation actually changes
725
+ the serialized instance. It does not forward incidental DOM control traffic: focus,
726
+ blur, paging, read-only controls, and a write that leaves `currentMetadata` unchanged
727
+ produce no event. Field edits, clears, controlled-term selections, and multi-instance
728
+ add, copy, and delete operations do.
305
729
 
306
- An example in Angular is:
730
+ The event is a `CustomEvent<CeeChangeDetail>`. Its detail carries the operation,
731
+ template path, supplied value, current validity and full data-quality report, plus
732
+ the current title and description. Multi-instance details also retain their former
733
+ `message` name for compatibility.
307
734
 
308
- - `component.html`:
309
- ```html
310
- <cedar-embeddable-editor
311
- [config]="conf"
312
- [templateObject]="template"
313
- [instanceObject]="instance"
314
- (change)="logChange($event)"
315
- ></cedar-embeddable-editor>
316
- ```
735
+ The package's custom-element declaration types the listener and its detail without
736
+ a cast:
317
737
 
318
- - `component.ts`:
319
738
  ```typescript
320
- logChange(event) {
321
- console.log('CHANGE', event);
322
- }
739
+ import type { CeeChangeDetail } from 'cedar-embeddable-editor';
740
+
741
+ const cee = document.querySelector('cedar-embeddable-editor');
742
+ if (!cee) throw new Error('CEE element is missing');
743
+
744
+ cee.addEventListener('change', (event) => {
745
+ const detail: CeeChangeDetail = event.detail;
746
+ console.log(detail.operation, detail.path);
747
+ console.log('valid:', detail.validity);
748
+ });
323
749
  ```
324
750
 
751
+ Framework event bindings receive the same custom event. For example, Angular can
752
+ bind `(change)="logChange($event)"` on the element and type the handler parameter as
753
+ `CustomEvent<CeeChangeDetail>`.
754
+
755
+ The optional `eventHandler.valueChanged(path, value)` callback receives the same
756
+ field mutations. It is not a dirty flag: only the host knows which serialization
757
+ was last loaded or saved. Keep that baseline in the host and compare
758
+ `cee.currentMetadata` after each `change`; doing so also clears dirty state when an
759
+ edit is undone.
760
+
325
761
  ### Viewer Mode
326
762
 
327
763
  CEE can be used as a viewer to display metadata instances. This can be achieved by the following configuration setting:
@@ -332,11 +768,17 @@ CEE can be used as a viewer to display metadata instances. This can be achieved
332
768
  When used in this mode, users won't be able to manipulate the metadata instance but can only read it.
333
769
  ## Example Applications
334
770
 
335
- There is a sample applications you can use to demonstrate how to embed and use CEE.
336
- Follow the links below to the demo application of your choice. The documentation for each demo application can be found in the README file of the corresponding application.
771
+ [`cedar-component-demo`](https://github.com/metadatacenter/cedar-component-demo)
772
+ holds small runnable applications that embed CEE, each with its own README:
337
773
 
338
- ### CEE Demo Angular
774
+ | Application | Framework |
775
+ |---|---|
776
+ | `cedar-cee-demo-angular-src` | Angular |
777
+ | `cedar-cee-demo-react` | React |
778
+ | `cedar-cee-demo-ember-src` | Ember |
339
779
 
340
- This demo is written in Angular 2 and requires that framework to run properly.
780
+ Each edits the same template, `eDNA ECT Demonstration`, kept as a file inside the
781
+ application rather than fetched from a server.
341
782
 
342
- https://github.com/metadatacenter/cedar-cee-demo/tree/main/cedar-cee-demo-angular-src
783
+ `cedar-cee-demo-angular-src` needs `npm install --legacy-peer-deps`; the others
784
+ do not.