cedar-embeddable-editor 1.5.2 → 2.0.2

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/CHANGELOG.md CHANGED
@@ -5,6 +5,1038 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ## [2.0.2] - 2026-08-27
11
+
12
+ The second stable release on the 2.x line adds field identities to required-value validation and
13
+ bundles the public `cedar-model-typescript-library@1.0.3`. The application and the visual fixtures
14
+ use that same model build, so the package exercised in browsers carries the contract those fixtures
15
+ were generated against.
16
+
17
+ ### Added
18
+
19
+ - The data quality report now includes one `required` problem for each unsatisfied required field
20
+ declaration, with the same `path`, `field`, and `inputType` carried by other problems. The report's
21
+ four-member shape, required-field counters, and validity result are unchanged; hosts that already
22
+ read `problems` gain the field identities that the counters alone could not provide.
23
+
24
+ ### Changed
25
+
26
+ - The application and visual-test dependency graphs now use the public
27
+ `cedar-model-typescript-library@1.0.3` release from npmjs.
28
+
29
+ ## [2.0.1] - 2026-08-21
30
+
31
+ The first stable release on the 2.x line, published to npmjs as `cedar-embeddable-editor@2.0.1`.
32
+ It takes its model library from the public registry — `cedar-model-typescript-library@1.0.2` in
33
+ place of the scoped Nexus snapshot the `2.0.0-dev.*` builds carried — so an embedder installs CEE
34
+ and the model it speaks from npmjs alone. The entries below were accumulated across those
35
+ snapshots, which are recorded individually further down.
36
+
37
+ ### Fixed
38
+
39
+ - A host's malformed template no longer spends the one assignment it gets. `templateObject`
40
+ and the template half of `templateAndInstanceObject` are set-once, so a payload that the
41
+ parser could not read used up the claim and left the element permanently empty with no
42
+ way to correct it. Each is now parsed into a throwaway context first, and rejected with
43
+ the parser's own reason before the claim is taken.
44
+
45
+ - Consecutive page breaks produce the blank pages they describe, at the positions they
46
+ describe. The count of breaks in a row was carried forward and the blank pages emitted
47
+ after the next page of content, so a template that broke twice in the middle showed its
48
+ blanks in the wrong order; a trailing break is still a final blank page.
49
+
50
+ - A widget that a new occurrence has no value for is cleared rather than left showing the
51
+ previous one. The same widget instance is reused as a multi-element pages, and a child
52
+ absent from the occurrence being shown was simply skipped, so the value from the
53
+ occurrence before it stayed on screen while the model held nothing. A temporal field
54
+ clears every part of the instant it had, not only its text.
55
+
56
+ - Paging between two controlled terms carries the term rather than its label. The registry
57
+ handed an editable controlled field the label alone, so the widget lost the IRI it needs
58
+ for the term's link and could not tell two occurrences with the same label apart.
59
+
60
+ - Declared text, textarea and controlled-term defaults now enter a newly built
61
+ instance before any widget renders, just as selected choice defaults do. A
62
+ controlled default keeps both its IRI and label, optional multi-choice defaults
63
+ create their own occurrences, and rendering no longer overwrites an explicitly
64
+ blank instance supplied by a host.
65
+
66
+ - Declared numeric and temporal defaults now follow that same instance-first path.
67
+ Numbers retain their declared XSD datatype; temporal defaults at year, month,
68
+ day, hour, minute, second and fractional-second granularity are normalized to
69
+ complete instance values. Editable controls receive the seeded value, while a
70
+ read-only template view labels it as a default rather than as recorded data.
71
+
72
+ - A read-only field's occurrence chips no longer land on the facts beside its name.
73
+ The chips are pulled 33px up onto the field's title row, which saves a row while
74
+ the form is editable and that row holds only the field's name; read-only the same
75
+ row carries the field's terse facts. The read-only placement that sits below the
76
+ row had a name nothing matched, so it had never applied.
77
+
78
+ - A read-only choice group no longer behaves like a control. The value was always
79
+ safe — a change in read-only is reverted before it reaches the instance — but the
80
+ group kept a pointer cursor, a hover ripple, a focus ring and the tab order, so it
81
+ flickered when clicked and arrow keys moved the selection before it snapped back.
82
+
83
+ - A read-only checkbox draws its box inside its own row. Material insets the box
84
+ 11px absolutely to centre it in the 40px control, and read-only shrinks that
85
+ control to 22px, so the box hung below the row and into the label.
86
+
87
+ - A single-class value constraint is stated as a value rather than as a class. It
88
+ enumerates what the field permits, so `class disease (DOID)` put jargon in front
89
+ of a bare label; it reads `value disease (DOID)`.
90
+
91
+ - A choice field with no option selected by default no longer records the empty
92
+ string. That was a third state beside the two an empty field has — `@value:
93
+ null` for a literal and `{}` for an IRI — and the compact serialization, the one
94
+ that omits an empty field, listed exactly the unanswered radios because of it.
95
+
96
+ - A read-only temporal box shows each part of the instant cut to the granularity
97
+ the field records. It showed the stored value whole, so a day-granularity field
98
+ asserted a midnight nobody entered and a to-the-minute field a zero second, with
99
+ a serialization's `T` between the halves.
100
+
101
+ - A value a host pushes into a read-only form is no longer blanked on arrival. The
102
+ rule that clears a template's declared default out of the control tested the mode
103
+ alone, and the view-to-model sync runs on every push; it compares against the
104
+ declared default now.
105
+
106
+ - A populated multi-select no longer shows a red dropdown arrow. Its Angular
107
+ control holds an array, and shared choice validation coerced that array to one
108
+ comma-joined string before checking membership, so two valid selections were
109
+ rejected as one nonexistent option. Each selected label is now validated
110
+ independently; genuine undeclared values remain invalid.
111
+
112
+ - A language map named after the artifact now loads. `languageMapPathPrefix` arriving in a `config`
113
+ assigned after the template was lost outright: no request for the map, and every built-in label
114
+ left in place. ngx-translate guards the work twice — `use()` returns at once when the language
115
+ asked for is already current, and behind it the loader is consulted only for a language it holds no
116
+ map for — and a host that renders first and configures second hits both, because the built-in map
117
+ is already loaded under `en` and the late config names `en` again. The map is now refetched and
118
+ republished, which is what makes already-rendered labels re-read it. Only maps loaded before the
119
+ configuration are refetched, so a first config and one naming a different language each fetch once.
120
+
121
+ - A configuration value CEE cannot use is refused rather than merely reported. The check said
122
+ "Ignored." and the reader then coerced the value: `readOnlyMode: 'false'` locked the form, since a
123
+ non-empty string is truthy, and `terminologyBaseUrl: 7` built the endpoint
124
+ `7bioportal/integrated-search`. A base URL missing its trailing slash was used as well, producing
125
+ `…/terminologybioportal/…`. Every such value now reads as unset, so the setting keeps the default
126
+ it documents, and one bad key costs only that key. CEE does not repair a value either: appending
127
+ its own path to a URL nobody wrote would name an endpoint nobody chose.
128
+
129
+ - A `config` that is not an object no longer spends the one assignment there is. A host that handed
130
+ over a string was told the configuration was ignored and then had its next, correct assignment
131
+ refused as a second one, leaving an element that could never be configured.
132
+
133
+ - An element assigned a template and no `config` now renders. It did not: the editor waited for a
134
+ configuration before building, so a host that wanted every default — which every key on
135
+ `CeeConfig` documents, all of them optional — had no way to say so. The element stayed blank for
136
+ good, `currentMetadata` answering `{}` and `currentMetadataYaml` answering `''`, with no error, no
137
+ warning, and nothing tying an empty frame to a key nobody had set. An unset configuration and `{}`
138
+ now mean the same thing.
139
+
140
+ Rendering therefore no longer waits for configuration, so for the first time a `config` can arrive
141
+ after the editor is built. It still applies: what it carries reaches already-built widgets through
142
+ services they subscribe to, rather than being read once at construction. The visible cost is that
143
+ a template followed by a config initialises twice, the second time replacing the defaults the
144
+ first installed.
145
+
146
+ The gap was invisible to the suites because every test host assigned a configuration — the visual
147
+ harness page always sets one, so nothing ever exercised the smallest thing a host can do. It
148
+ surfaced from the e2e smoke, whose own check had been passing vacuously: it asserted that the
149
+ metadata "is an object", and `{}` is one.
150
+
151
+ ### Added
152
+
153
+ - A host is told what changed rather than that something did. The `change` event carries a
154
+ `CeeChangeDetail` naming the operation, the template path, the value supplied to it, the
155
+ resulting validity, the whole data-quality report, and the instance's title and
156
+ description; `valueChanged(path, value)` on the event handler receives the same field
157
+ mutations. It fires when the serialized instance actually changed, so focus, blur, paging,
158
+ a read-only control and a write that leaves `currentMetadata` identical produce nothing.
159
+ What a host had before was whatever `change` bubbled out of the root element, which named
160
+ no field and could not distinguish an edit from an edit undone. Dirty state stays the
161
+ host's to keep, since only the host knows which serialization it last loaded or saved.
162
+
163
+ - The published element type declares the `change` listener, so a TypeScript host reads
164
+ `event.detail` as `CeeChangeDetail` without a cast, and the inherited overloads still
165
+ cover every other DOM event.
166
+
167
+ - A host can be told when the form is first on screen. `ready` was declared on
168
+ `CeeEventHandler` and called from nowhere, so an embedder that wanted to act once
169
+ the widgets existed had to poll the DOM for them. It now fires once, after this
170
+ element's first completed render, and it does not fire for an artifact CEE
171
+ refused; a handler attached after that render is not sent a replay.
172
+
173
+ - The identifier of a controlled term or an external-authority value is a link when
174
+ the form is read with a value in hand. It was text inside a readonly `input`, which
175
+ cannot contain an anchor, so a reader had to select and paste it. The authority's own
176
+ link-out keeps its place beside it — for a controlled term those are two different
177
+ destinations: the IRI is what the instance records, the icon is the term's page in
178
+ its ontology. Only an `http` or `https` identifier is linked; anything that
179
+ identifies without locating renders as text, and link fields are left alone.
180
+
181
+ - The header states what the template says about itself — its `pav:version` and
182
+ whether it is a draft or published — on the icon's row, right-aligned above the
183
+ controls. A reader of a form wants to know which revision produced it and
184
+ whether that revision can still change under them. A template declaring neither
185
+ states nothing rather than guessing.
186
+
187
+ - A form read with no instance behind it states each field instead of showing an
188
+ empty control: how many values it takes, the shape of one, the permitted values,
189
+ the pattern, the unit, the declared default, and the authorities a controlled
190
+ field draws on, each linked to its BioPortal page. The statement goes in a box
191
+ the size of the control it replaces, so the page still reads as the form it
192
+ stands for, and it wraps where placeholder text would truncate. Radio and
193
+ checkbox groups keep their controls — a set of options already shows what the
194
+ values are — and the option carrying the declared default is marked there.
195
+ Every control returns when an instance is supplied, since then the box has a
196
+ value to show.
197
+
198
+ - `Compact YAML - Instance` in the download menu writes the model library's
199
+ compact instance form to `<name>-instance-compact.yaml`, omitting root identity
200
+ and provenance metadata while retaining the instance data.
201
+
202
+ - `Compact YAML - Template` in the download menu writes the model library's
203
+ compact template form to `<name>-template-compact.yaml`, alongside the full
204
+ YAML document rather than replacing it.
205
+
206
+ - `CeeValidationProblem` declares `field` and `inputType`. Every problem has carried both and the
207
+ validation guide documents both, so the one kind of consumer the declarations exist for was the
208
+ only one that could not read them without a cast.
209
+
210
+ ### Changed
211
+
212
+ - Every timer and subscription a widget owns ends with the widget. The authority and
213
+ controlled-term fields held `setTimeout` callbacks for opening the autocomplete panel and
214
+ for expiring the reverted and cleared notices, and a panel-closing subscription with no
215
+ teardown, so a field destroyed inside those windows left work that ran against a dead
216
+ component. They are `timer` piped through `takeUntilDestroyed` now.
217
+
218
+ - The seven authority widgets share one search and lifecycle implementation. ORCID and ROR
219
+ kept their own copies of the pipeline alongside the detail panels that are genuinely
220
+ theirs, so a change here had to be made three times; the panels stay with each widget and
221
+ the rest lives once in the base class.
222
+
223
+ - The host artifact inputs are accepted as one atomic state. `templateObject`,
224
+ `instanceJsonObject` and `templateAndInstanceObject` overlap and may arrive in any order,
225
+ and the wrapper coordinated them inline, which left a failure halfway through
226
+ initialization able to publish a half-replaced context. A coordinator now parses into a
227
+ candidate context and publishes the completed state, and the inner component renders what
228
+ it is given rather than parsing the artifact a second time.
229
+
230
+ - Widget subscriptions end with the widget through `takeUntilDestroyed` rather than a
231
+ hand-held `Subscription` per component, which removes the teardown each of the six had to
232
+ implement.
233
+
234
+ - Work that has to happen after Angular has rendered waits for the render rather
235
+ than for a timer. Pushing model values into live widgets, and the same push after
236
+ a page change or an occurrence added, copied or deleted, was scheduled with
237
+ `setTimeout` — a guess that the components would exist by the next task, taken
238
+ once per call site and in one case twice for one click. One scheduler now owns
239
+ that wait, built on `afterNextRender`, and a newer state supersedes an older one
240
+ instead of both being pushed: several host inputs arriving in one turn, or a
241
+ reader paging faster than the form renders, no longer race. A failed push is
242
+ reported through the message handler rather than lost in a callback.
243
+
244
+ - The required-field asterisk is the colour of the label it belongs to rather than
245
+ red. A form of required fields opened covered in error-coloured marks before
246
+ anybody had done anything, and a missing value is already reported in the error
247
+ line and in the data-quality report.
248
+
249
+ - `eventHandler` is documented as replaceable, and replacing one is traced. The published contract
250
+ said every member of the element keeps its first assignment, which was false for the handler and
251
+ meaningless for the three read-only getters. Set-once protects the inputs that decide what the
252
+ editor is; a callback slot decides nothing about the form, and sealing it would have answered a
253
+ host's second assignment by reporting the refusal to the handler being replaced. A handler still
254
+ hears only what follows it, so a page wanting the diagnostics from configuration registers it
255
+ first.
256
+
257
+ - The ROR mark is inlined instead of fetched. It was pulled from
258
+ `raw.githubusercontent.com/ror-community/ror-logos/main/…` while a form rendered, so every
259
+ embedding told GitHub that someone was looking at a ROR field, from the host application's origin
260
+ — in a component whose fonts and stylesheets travel inside the bundle precisely so nothing is
261
+ fetched. An offline deployment drew no icon, and the URL named a branch rather than a commit, so
262
+ the asset could change without a release. It also decoded late often enough to move two visual
263
+ baselines by 91 pixels a run, which is what surfaced it.
264
+
265
+ - **BREAKING.** A host names two CEDAR servers and nothing below them. `terminologyIntegratedSearchUrl`
266
+ becomes `terminologyBaseUrl` and `extAuthBaseUrl` becomes `bridgeBaseUrl`, each taking a server
267
+ alone, with CEE appending the routes: `bioportal/integrated-search` on one side, `ext-auth/` and
268
+ the seven authorities' fourteen paths on the other. Those routes belong to the servers they
269
+ address, and hosts had been spelling them out — the terminology endpoint whole, and the bridge's
270
+ `ext-auth/` segment — so they stood written in four deployment configs that would have had to
271
+ change together.
272
+
273
+ Neither key has a default now. `extAuthBaseUrl` held a `.orgx` hostname for a year, which resolved
274
+ nowhere off the machine it was written on, and then the production bridge, which the two frontends
275
+ that never set the key reached from a local stack without asking or knowing.
276
+
277
+ Both bases must end in a slash, and both are validated for it. Unset, each turns its lookups off
278
+ and CEE reports which key is missing, once, rather than a form of working-looking fields that find
279
+ nothing. That replaces two different silences: controlled-term search returned an empty result
280
+ indistinguishable from a term nobody has, and an authority field threw on every keystroke — right
281
+ while endpoints were always registered from a default, and wrong once their absence became the
282
+ ordinary case of a host that configured no lookups.
283
+
284
+ - **BREAKING.** The eight diagnostic panels become a download menu, behind one key.
285
+ `showDownloadMenu` replaces sixteen: eight `show…` keys and their eight `expanded…`
286
+ partners. It defaults to `false`, where `showTemplateSourceData` and `showInstanceDataFull`
287
+ defaulted to `true` — so an embedder who configured nothing used to get a JSON Schema dump
288
+ and a JSON-LD dump under every form. The visual suite had been documenting that: its base
289
+ preset switched five panels off and never the sixth, so all 48 fixture baselines carried a
290
+ collapsed JSON-LD panel, which is the 40px every one of them lost. CEE now renders no dumps
291
+ at all; each view is saved as a file named from the template, `AttributeValues-instance.yaml`
292
+ rather than `instance.yaml`, so several open forms do not collide. `SourcePanelsComponent`
293
+ and its 207-line template are gone, replaced by a descriptor list, a pure
294
+ `downloadContentFor` the harness can ask without a browser, and a menu that holds no state.
295
+ Downloads are page-initiated and a sandboxed host can refuse one with no observable event,
296
+ so each attempt is traced through the event handler.
297
+
298
+ - **BREAKING.** `trustTemplateMarkup` is renamed `trustTemplateRichText`, with no alias. The old
299
+ name claimed a surface far wider than the one it has: a reader could reasonably expect it to
300
+ govern field descriptions, help text or labels, none of which render as HTML. CEE renders HTML
301
+ in exactly two places — the body of a static rich-text field, from the template, and a field
302
+ value in the read-only view, from the instance — and this key governs the first and can never
303
+ govern the second. The new name states both the provenance that makes the trust decision the
304
+ host's to make, and the single surface it applies to. `TemplateTrustService` renames its
305
+ members to match. A host still passing the old key is told the key is unknown and falls back
306
+ to sanitizing, which is the safe direction but a visible change: rich text styled beyond the
307
+ sanitizer's policy will render flattened until the key is renamed.
308
+
309
+ ### Removed
310
+
311
+ - A placeholder component and an RDF pipe that nothing rendered.
312
+
313
+ - `Template Rendering Data`, `Multi-Instance Information` and the duplicate
314
+ `JSON-LD - Instance - Core` are no longer in the download menu. The first two
315
+ exposed CEE's private working state rather than a portable CEDAR artifact;
316
+ removing the exports does not remove the internal rendering tree or occurrence
317
+ tracking that the editor itself still uses. `Core` had become a legacy alias
318
+ that downloaded exactly the same canonical document as `JSON-LD - Instance`.
319
+
320
+ - **BREAKING.** CEE mints no element-occurrence identifiers. It stamped a fresh GUID onto every
321
+ occurrence it built, under `https://repo.metadatacenter.org/template-element-instances/`, on the
322
+ grounds that CEDAR requires an `@id` there. A template's element sub-schema does name `@id` in its
323
+ `required` list, but the validator does not enforce a value for it: measured against the canonical
324
+ `CedarValidator`, an occurrence validates with the key null and with the key absent, and is
325
+ rejected only for a string that is not a URI. The requirement being met did not exist, and what
326
+ was minted was an identity the artifact does not have — different on every build of the same form,
327
+ naming a repository that has never heard of it. An `@id` on an occurrence now only ever arrives in
328
+ a loaded instance, and CEE leaves that one alone: duplicating an occurrence clears the copy's
329
+ rather than reminting it.
330
+
331
+ Two builds of the same template are now the same document, which they never were. The harness
332
+ carried a `normalize` that rewrote every minted identifier to `<minted>` before any comparison, so
333
+ that instance snapshots recorded a value meaning nothing; it and four such recordings are gone.
334
+ `addRandomAtId`, `getTemplateElementInstanceIRIPrefix` and `util/iri-prefix.ts` go with them.
335
+
336
+ - **BREAKING.** `iriPrefix`. It set the prefix an element occurrence's minted identifier was built
337
+ under, and what identified an occurrence was the GUID appended to it — so the prefix carried
338
+ nothing a host could usefully vary, and every host that set it named its own deployment's
339
+ repository, which nothing resolves and the identifier did not otherwise mention. The key went
340
+ first and the minting followed, so nothing is built under any prefix now; the entry above is the
341
+ whole of the current behaviour.
342
+
343
+ The key was the only reason `IriPrefix` was a class provided per element rather than a constant,
344
+ so its provider, its three injections — one of which, in the text widget, read it nowhere — and
345
+ the function threaded through `HandlerContext` into `DataObjectBuilderHandler` all went with it.
346
+ Removing the minting then took the constant and `util/iri-prefix.ts` too. What that file existed
347
+ to prove outlives it and is still guarded by `import-boundaries.spec.ts`: the value once lived on
348
+ the editor component, and reading it from the domain layer dragged the whole Angular subtree in
349
+ behind it.
350
+
351
+ - **BREAKING.** All fourteen per-authority endpoint keys, `<name>IntegratedExtAuthUrl` and
352
+ `<name>IntegratedDetailsUrl`. Each named a path appended to the bridge server's base — the search
353
+ path for a name typed into the field, the details path for an identifier pasted into it —
354
+ and every host that set one set the value CEE already uses, with NIH Grant's and DOI's
355
+ never named by any host at all. The paths are the bridge server's routes, which
356
+ `bridgeBaseUrl` already identifies, so a host free to move them could only move them
357
+ somewhere nothing answers. Both endpoints are unchanged and still used: pasting an ORCID,
358
+ a DOI or a PubMed ID resolves through the details path rather than running a name search.
359
+ A deployment now moves all fourteen endpoints by moving the base URL, or none of them, and
360
+ a host still passing a retired key is told it is unknown and ignored.
361
+
362
+ With them goes the index signature on `CeeConfig`, which existed to carry them. The
363
+ interface is closed: every key a host can set is declared, so a misspelling is a compile
364
+ error rather than a silent no-op reported only at runtime. `CeeConfigKey` becomes
365
+ `keyof CeeConfig` — it was `Exclude<keyof CeeConfig, number | symbol>`, which is what an
366
+ open interface costs. `CeeAuthority` is removed; it named the authorities only to describe
367
+ the keys that are gone.
368
+
369
+ - **BREAKING.** `bioPortalPrefix`, and a broken link it half-governed. It was named as a prefix
370
+ and used as a base for the "read about this term" link out to BioPortal's web UI, which is
371
+ BioPortal's address rather than a deployment's to set. It governed only two of the three
372
+ constraint kinds that reach that link: a branch was linked through its own `source`, and
373
+ `source` is not a URL. Across the corpus a branch carries `"Medical Subject Headings (MESH)"`,
374
+ or the FDC-GDMT ontology's full name, or occasionally a bioportal.bioontology.org URL — so two
375
+ shapes out of three produced `Medical Subject Headings (MESH)?p=classes&conceptid=…`, a
376
+ relative link resolved against whatever page CEE was embedded in. Every kind is now built the
377
+ same way, from the acronym each reliably carries, and the acronym is escaped rather than
378
+ concatenated. The link moves to `bioPortalTermLink`, a plain function the harness covers
379
+ against the real constraint shapes, where nothing covered it before. `IriPrefix` is left
380
+ holding the one prefix that is genuinely a prefix.
381
+
382
+ - **BREAKING.** `orcidPrefix` and `rorPrefix`. Neither was a prefix: nothing minted or built a
383
+ URL from them, they recognised one. Each was interpolated straight into
384
+ `new RegExp('^' + prefix)`, so every `.` in the configured URL matched any character and a
385
+ prefix carrying a regex metacharacter matched something else again or threw. They also existed
386
+ for two of the seven authorities CEE knows, so the same value in a DOI, PubMed, RRID, PFAS or
387
+ NIH Grant field got none of the treatment. What they gated stays: a read-only text field holding
388
+ an `https://orcid.org/` or `https://ror.org/` value still renders as a link with the registry's
389
+ icon, showing the identifier rather than the whole IRI. It now tests fixed constants with
390
+ `startsWith` and builds no regex at all, because a registry's own IRI is not a deployment's to
391
+ configure. The two prefixes that were genuinely a deployment's — `iriPrefix`, which minted IRIs
392
+ into the instance, and `bioPortalPrefix`, which built the link out to BioPortal's web UI — are
393
+ retired in their own right, above and below.
394
+
395
+ - **BREAKING.** `showHeader` and `showFooter`, and the header and footer they gated. CEE drew
396
+ a `mat-toolbar` carrying the CEDAR logo and the title "CEDAR Embeddable Editor", and a footer
397
+ carrying the Stanford Division of Computational Medicine mark, the maintainer line and a
398
+ contact link. Every string and every destination was hardcoded, so an embedder could take
399
+ CEDAR's identity or nothing, and the key names said "header" and "footer" as though a host
400
+ could put its own there. An embedded component has no business drawing the page around
401
+ itself: a host renders its own, and the standalone developer app now does exactly that as a
402
+ worked example. The CEDAR mark and the version stamp stay, inside the form's own title block,
403
+ which is a component naming itself rather than dressing someone else's page. Gone with them:
404
+ the `App.Title`, `App.Maintained` and `App.Contact` translations from both language maps, and
405
+ the visual suite's `chrome` preset and its two baselines. The suite's only rendered surface
406
+ for an externally served translation was the footer, so that coverage moves onto the form's
407
+ own Expand All label, which renders on every template behind no key.
408
+
409
+ - **BREAKING.** `inputSerialization`, `outputSerialization` and the
410
+ `currentMetadataSerialized` accessor that existed for the second of them. CEE now picks the
411
+ template reader from the template. The two CEDAR serialisations do not resemble each other,
412
+ and it is measured rather than assumed: across the 37 corpus templates, each shipped in both
413
+ forms, eighteen top-level keys appear in every JSON template and in no YAML one — `@context`,
414
+ `@type`, `properties`, `$schema`, `_ui` and the `pav:` and `schema:` families among them —
415
+ while `modelVersion`, `name`, `status` and `version` appear in every YAML template and in no
416
+ JSON one, with no key shared between the sets. All 94 JSON templates the harness carries,
417
+ including vendored HuBMAP production artifacts, have both `@context` and `properties`. So
418
+ `inputSerialization` asked a host to declare what the artifact already states.
419
+ `outputSerialization` only chose what a third output accessor returned; `currentMetadata` and
420
+ `currentMetadataYaml` are unconditional and unchanged, so a host reads whichever it wants and
421
+ the call site now says which format it expects. YAML template support is untouched:
422
+ `parser-selection.spec.ts` asserts every corpus template in both forms selects the reader that
423
+ can read it, and `format-independence.spec.ts` still requires both readers to produce identical
424
+ trees.
425
+
426
+ - **BREAKING.** CEE no longer fetches artifacts. The sample-template loader took
427
+ `sampleTemplateLocationPrefix` and `loadSampleTemplateName`, built
428
+ `<prefix><name>/template.json` and `<prefix><name>/metadata.json`, fetched both and
429
+ assembled them into `templateAndInstanceObject`. It was the only path where CEE reached
430
+ the network for an artifact, and the only one that could reassign one — it bypassed the
431
+ assign-once claims deliberately, because it loaded a different sample on every click.
432
+ A host supplies its artifact by assigning a parsed object, which is what every route
433
+ now does. Removed with it: `showSampleTemplateLinks` and `expandedSampleTemplateLinks`,
434
+ which showed the picker listing what had been fetched; `SampleTemplatesService`, the
435
+ two picker components and the `SampleTemplateLoaderOwner` model; and the
436
+ sample-registry fixtures the visual suite served. The standalone developer app now
437
+ fetches its own demo from `src/assets/cee-demo` and assigns it, like any other host.
438
+
439
+ - **BREAKING.** `hideEmptyFields`, and the empty-field hiding it switched on. In read-only mode
440
+ it dropped every field the loaded instance had no value for. It worked only when the artifact
441
+ arrived on `templateAndInstanceObject`: the form is built when the template lands, and on the
442
+ two-input route nothing has read the instance by then, so no field is yet known to be empty.
443
+ Three of the six known consumers use that route, where the key silently did nothing and the
444
+ validator said nothing either. Rebuilding it properly means changing when the form is built,
445
+ which is the same ordering the assign-once contract rests on, so it is removed rather than
446
+ half-fixed. Gone with it: `HandlerContext.hideEmptyFields` and `enableEmptyFieldHiding`, the
447
+ factory's `applyEmptyFieldHiding`, `hasNonEmptyChild` and `getValueByPath`, and
448
+ `ActiveComponentRegistryService.setVisibility` with the `getFieldComponents` it used. Fields
449
+ hidden by the template's own `_ui.hidden` are unaffected — that is `hiddenInTemplate`, a
450
+ separate flag on a separate path, and it still hides.
451
+
452
+ - **BREAKING.** `showSpinnerBeforeInit`, and the placeholder it switched on. Before a template
453
+ arrived the wrapper drew a 24px indeterminate spinner beside the translated string "CEDAR
454
+ Embeddable Editor initializing...", and a host could suppress it but not replace it — so an
455
+ embedder's only choices were CEDAR's branding during every load or an empty box. The editor
456
+ renders as soon as `editorDataReady()` is true either way; nothing now occupies the interval
457
+ before it. Gone with it: the `Process.Initializing` translation group from both language maps,
458
+ the `.spinner-wrapper` style, and `MatProgressSpinnerModule` from the shared module, which no
459
+ remaining component there uses.
460
+
461
+ - **BREAKING.** `showAllMultiInstanceValues`, and the "All Values" summary it switched on.
462
+ The summary listed every occurrence of a multi-instance field above that field, and had
463
+ never once rendered as designed. Its occurrence numbers were meant to be the grey chips the
464
+ pager itself uses — `.multiinfo-index` styles them, and `.not-first-multiinfo-index` puts a
465
+ 15px gap before each one — but the strip was built as an HTML string and injected through
466
+ `[innerHTML]`, and Angular's emulated encapsulation scopes component styles by an
467
+ `_ngcontent-*` attribute that injected nodes never receive. So none of it applied: the
468
+ numbers rendered as bare text with no gap, and `1 Alpha2 Beta3 Gamma` read as four values
469
+ rather than three. An occurrence with no value printed the literal word `null` on top of
470
+ that. Gone with it: `getMultiInstanceDataValueInfo`, `shortValue` and the 30-character cap,
471
+ the pager's `ngDoCheck`, the `Generic.AllValues` translation in both language maps, the
472
+ three `multiinfo-index` rules, and the global `.info-box` style they used.
473
+
474
+ - **BREAKING.** `collapseStaticComponents`, and the collapsing it switched on. A lone image,
475
+ video or rich-text static immediately before a field or element was removed from the sibling
476
+ list and re-attached inside that successor, recursively through nested elements. For an
477
+ element it went further and replaced the element's own heading with the static's label, so a
478
+ group of questions could lose its name to a decorative video: in the `18-real-nested` fixture
479
+ the panel titled "All Field Types (single)" rendered as "YouTube Video". Static content now
480
+ renders where the template puts it, which is what the key's own default already did. Gone with
481
+ it: `linkedStaticFieldComponent` from the component model, the
482
+ `CedarComponentLinkedStaticFieldHeaderComponent` that drew the substituted heading, and the
483
+ `cee-element-content-with-static` spacing rule.
484
+
485
+ - **BREAKING.** `showStaticText`. The key read as a switch over a template's static content
486
+ and was never that. It could hide only a lone image, video or rich-text block that the
487
+ renderer had absorbed into the item following it; section breaks, page breaks and any static
488
+ paired with another static were untouched. It was also consulted on only one of the two
489
+ branches that draw an absorbed static, the one where the item is a field rather than an
490
+ element, so whether the key did anything depended on what a template author happened to put
491
+ after the static. It defaulted to on, and removing it leaves rendering unchanged for every
492
+ template in the fixture corpus. A host still passing the key is told it is unknown, by the
493
+ same configuration validator that reports any other unrecognised key.
494
+
495
+ ## [1.6.0] - 2026-08-13
496
+
497
+ The first stable release since 1.5.2, published to npmjs as `cedar-embeddable-editor@1.6.0`.
498
+ It carries the Angular 14 → 22 migration and a host contract that is now stated rather than
499
+ implied. The dated `1.6.0-dev.*` sections below record how it was reached, one build at a
500
+ time; this section is what changed between 1.5.2 and 1.6.0.
501
+
502
+ ### Added
503
+
504
+ - TypeScript declarations for the host contract, shipped with the package: `CeeConfig`,
505
+ `CeeEventHandler`, `CeeDataQualityReport` and the artifact types. A host now gets a compile
506
+ error for a misspelled key or a wrong value.
507
+ - Configuration validation where a configuration crosses the custom-element boundary. Unknown
508
+ keys, wrong value types and settings that contradict each other are reported through the
509
+ event handler. Reporting only: a bad key is ignored as it always was, and the host is told.
510
+ - YAML as an artifact serialization, both directions. `inputSerialization` accepts a template
511
+ parsed from CEDAR YAML, `outputSerialization` selects the form `currentMetadataSerialized`
512
+ returns, and `currentMetadata` and `currentMetadataYaml` are always available regardless.
513
+ - Source panels showing the template and the live instance as CEDAR YAML, through
514
+ `showTemplateYaml` and `showInstanceYaml`, each expanding independently.
515
+ - `trustTemplateMarkup`, for hosts whose template authors are as trusted as their own code.
516
+ - `--cee-element-heading-size`, `--cee-element-heading-weight` and `--cee-element-content-gap`
517
+ as host-settable custom properties.
518
+
519
+ ### Changed
520
+
521
+ - **BREAKING.** Every input on the custom element takes one assignment and keeps it. A second
522
+ assignment to `config`, `templateObject`, `instanceObject` or `templateAndInstanceObject` is
523
+ reported through the event handler and ignored, and the first value stands. A host wanting
524
+ different configuration or a different artifact creates a new element. The element previously
525
+ only accumulated state, so it could not be returned to a known state and the same assignments
526
+ in a different order produced a different editor.
527
+ - **BREAKING.** `readOnlyMode` is the only way in or out of read-only mode, and it reaches the
528
+ widgets directly rather than through a UI control.
529
+ - **Angular 14.3 → 22.1.** Eight major versions. The build runs through
530
+ `@angular/build:application` and the webpack `browser` builder and
531
+ `@angular-devkit/build-angular` are gone; all 203 `*ngIf` and `*ngFor` sites moved to block
532
+ control flow; TypeScript `strict` is on throughout, including the domain harness. Building
533
+ CEE now requires Node `^24.15.0`. The published package declares no `engines` and no
534
+ dependencies, so this constrains building CEE, not embedding it.
535
+ - **The instance CEE edits is a model rather than a CEDAR document.** CEE names no CEDAR
536
+ serialization key outside its two wire adapters, and reads no key constant from the model
537
+ library. Requires `cedar-model-typescript-library@1.0.0`.
538
+ - Each editor owns its own services, endpoints, language settings, preferences and IRI
539
+ prefixes, and releases its registrations and its shadow-local overlay and accessibility nodes
540
+ when destroyed. Two editors on one page no longer interfere.
541
+ - CEE and Angular Material styles are encapsulated in the custom element's shadow root.
542
+ - The temporal editors are rebuilt around one temporal value, covering date, time and timezone.
543
+ - A typed field reports its validation error on blur rather than on every keystroke.
544
+ - Read-only mode hides the multi-instance pager for a group holding one instance or none.
545
+ - An attribute name the user types is validated where they type it and refused with a message
546
+ under the field, rather than being renamed to `Attribute Value Field<N>` and reported in a
547
+ toast after the name had been thrown away.
548
+ - Timezone data is current. `moment-timezone` was pinned to a release carrying stale tzdb.
549
+ - The version names the commit whose content it carries.
550
+
551
+ ### Removed
552
+
553
+ - **BREAKING.** The preferences menu and its read-only switch, along with the
554
+ `showPreferencesMenu` key. The switch wrote to the same state the widgets read, so a form
555
+ embedded as a viewer could be made editable from inside it.
556
+ - **BREAKING.** `loadConfigFromURL`. It was a second way to spend the single configuration
557
+ assignment and raced a host that also assigned `config` directly. A host that keeps
558
+ configuration in a deployed file fetches it and assigns the result.
559
+ - **BREAKING.** `BrowserAnimationsModule`, and with it the `@angular/animations` dependency.
560
+
561
+ ### Fixed
562
+
563
+ - Attribute-value fields, across the whole surface: a field stays editable after a host saves
564
+ an instance and injects it back; it keeps the name the user typed; a copy is named
565
+ `<name> copy` and then `<name> copy 2` until the name is free; renaming or clearing one
566
+ occurrence no longer deletes a property another occurrence still carries; a slot the loaded
567
+ instance has no key for can be filled in; and the JSON-LD and YAML panels show the field as a
568
+ CEDAR document rather than CEE's internals.
569
+ - `hideEmptyFields: true` never survived startup, so the key did nothing. Both artifact setters
570
+ cleared it after the configuration had set it. It is honoured on `templateAndInstanceObject`;
571
+ on the two separate inputs the form is built before the instance is read, so no field is
572
+ known to be empty, and that limit is asserted rather than hidden.
573
+ - External authority fields distinguish a lookup that failed from one that matched nothing, no
574
+ longer read a response shape no authority sends, and no longer offer a populated field's own
575
+ value back as its one suggestion.
576
+ - A failed terminology lookup no longer ends a controlled-term field's `valueChanges` pipeline.
577
+ - An open suggestion panel stays with its field while the host page scrolls.
578
+ - The multi-instance pager marks the page it is actually showing, and its actions align with
579
+ its chips and reflow beneath them below 620px.
580
+ - Static fields: an image honours the width and height its template asks for and is centred, a
581
+ YouTube field renders at its declared size, an image URL that cannot be loaded is reported
582
+ instead of drawing an empty card, and a YouTube link that cannot be embedded explains why.
583
+ - A numeric field renders its unit only when the template declares one.
584
+
585
+ ### Security
586
+
587
+ - Static rich-text fields are sanitized by default. A template author's markup previously
588
+ rendered verbatim in the host's origin; `trustTemplateMarkup` opts back out.
589
+ - Links in template rich text that open a new tab are given `rel="noopener noreferrer"`.
590
+ - `lodash-es` moves to 4.18.1 and Vitest to 4.1.10, clearing the high-severity advisories a
591
+ production audit reported.
592
+
593
+ ## [1.6.0-dev.20260812.b953153] - 2026-08-12
594
+
595
+ ### Changed
596
+
597
+ - **BREAKING.** Every input on the custom element takes one assignment and keeps it.
598
+ A second assignment to `config`, `templateObject`, `instanceObject` or
599
+ `templateAndInstanceObject` is reported through the event handler and ignored, and
600
+ the first value stands; a host wanting different configuration or a different
601
+ artifact creates a new element. The element previously only accumulated state — a
602
+ second `config` patched the first for most keys and replaced it for
603
+ `outputSerialization`, and three inputs could each supply an artifact with nothing
604
+ saying which won — so a host could not return it to a known state, and the same
605
+ assignments in a different order produced a different editor. `templateObject` and
606
+ `instanceObject` remain independent and may be assigned in either order;
607
+ `templateAndInstanceObject` supplies what both do and cannot be combined with
608
+ either.
609
+ - **BREAKING.** `readOnlyMode` is the only way in or out of read-only mode, and it
610
+ now reaches the widgets directly. It used to travel through the preferences menu:
611
+ the host's flag was an input on that component, whose setter wrote to the state the
612
+ widgets subscribe to. So host configuration reached the form only by passing through
613
+ a UI control, which is how that control came to be able to override it, and why the
614
+ menu had to stay instantiated even when configured invisible or read-only never
615
+ arrived at all.
616
+
617
+ ### Removed
618
+
619
+ - **BREAKING.** The preferences menu and its read-only switch, along with the
620
+ `showPreferencesMenu` key that governed the menu. The switch wrote to the same state
621
+ the widgets read, so a form embedded as a viewer could be made editable from inside
622
+ it — and a host offering its own save button would then store the edits. Read-only is
623
+ the host's decision, and the menu had nothing else in it.
624
+ - **BREAKING.** `loadConfigFromURL`. It was a second way to spend the single
625
+ configuration assignment and raced a host that also assigned `config` directly; the
626
+ method carried a note saying CEE should not need to know how to fetch. A host that
627
+ keeps configuration in a deployed file fetches it and assigns the result.
628
+
629
+ ### Fixed
630
+
631
+ - `hideEmptyFields: true` never survived startup, so the key did nothing on the one
632
+ input that honours it. Both artifact setters cleared the flag, on the reasoning that
633
+ a new artifact invalidates a hiding decision made against the old one, and on the
634
+ single pass an artifact gets the clear ran after the configuration set it. Nothing
635
+ caught it because the only test of the key exercised the wrapper alone, with no child
636
+ editor and no template — it watched the flag being set and never saw either setter
637
+ run. The key now has behavioural coverage on the combined input, and the separate
638
+ inputs' failure to honour it is asserted as the build-ordering limit it is.
639
+
640
+ ## [1.6.0-dev.20260811.c67ccae] - 2026-08-11
641
+
642
+ ### Fixed
643
+
644
+ - The YAML source panel shows an attribute-value field the user has been editing. CEE
645
+ holds one as name slots plus sibling atoms, which is the shape the pager edits, and
646
+ the library's YAML writer understood only the packed form its own reader produces —
647
+ so the field name disappeared and its attributes were written as ordinary children.
648
+ The AV-only YAML output is asserted in the harness.
649
+
650
+ ### Changed
651
+
652
+ - Requires `@org.metadatacenter/cedar-model-typescript-library@0.9.2-dev.20260811.d87b47c`,
653
+ which fixes that and exports `AttributeValueNamePolicy` — the library's own account of
654
+ the namespace an attribute name enters, which the reserved set CEE holds locally was
655
+ written to match, and which its `InstanceValidator` now enforces.
656
+
657
+ ## [1.6.0-dev.20260811.feaebdb] - 2026-08-11
658
+
659
+ ### Added
660
+
661
+ - Two source panels showing the template and the live instance as CEDAR YAML, written
662
+ by the model library's YAML writer. Both are opt-in, through `showTemplateYaml` and
663
+ `showInstanceYaml`, and each expands independently through `expandedTemplateYaml` and
664
+ `expandedInstanceYaml`. The four keys are declared on `CeeConfig`, checked by the
665
+ config validator, and titled in the English and Hungarian language maps.
666
+
667
+ ### Changed
668
+
669
+ - The instance panels are written with the source template in hand, which is what the
670
+ model library's writer needs to produce the document a host reads back.
671
+ - The source panels' styling lives in the component's own stylesheet rather than in the
672
+ global sheet, and the panels are more compact. The visual baselines are re-recorded.
673
+
674
+ ## [1.6.0-dev.20260811.563e8b6] - 2026-08-11
675
+
676
+ ### Fixed
677
+
678
+ - The multi-instance pager marks the page it is actually showing. Selection was
679
+ declared on each chip with `[selected]`, which the chip listbox does not track, so
680
+ after cloning or paging the highlighted chip and the occurrence on screen could
681
+ disagree. The listbox now holds the selection and each chip declares its own value.
682
+
683
+ ### Changed
684
+
685
+ - A duplicate attribute name is reported as already used "in this instance" rather
686
+ than "on this object", which is what a person filling in a form is looking at.
687
+
688
+ ## [1.6.0-dev.20260811.72892c7] - 2026-08-11
689
+
690
+ ### Changed
691
+
692
+ - An attribute name the user types is validated where they type it, and refused with a
693
+ message under the field: one already used by a sibling attribute or reserved by a
694
+ template child, and one reserved for instance metadata, which is any name starting
695
+ with `@` plus the envelope and label keys. A collision used to be renamed to
696
+ `Attribute Value Field<N>` and reported in a toast, so a name the user had typed was
697
+ thrown away. The model is left alone while the name is invalid, and the error clears
698
+ when the field is loaded again. The reserved set is held here in step with the model
699
+ library's `AttributeValueNamePolicy` until the next library package is published.
700
+
701
+ ### Fixed
702
+
703
+ - Copying an attribute-value occurrence names the copy `<name> copy`, then `<name>
704
+ copy 2` and so on until the name is free, rather than producing a collision the
705
+ handler had to resolve. If no name is free the copy is left unnamed and the failure
706
+ is reported. The registry's guess at which occurrence was a clone — comparing a slot
707
+ with the one before it — is gone.
708
+ - Renaming or clearing one occurrence no longer deletes the property while another
709
+ occurrence still carries that name. The old check compared the name's first index
710
+ against the slot being edited, which is not the same question.
711
+
712
+ ## [1.6.0-dev.20260811.2db8763] - 2026-08-11
713
+
714
+ ### Fixed
715
+
716
+ - The "JSON-LD - Instance" panel, and its copy button, show the instance as a CEDAR
717
+ document. Both rendered `instanceFullData` through the `json` pipe, which is a
718
+ `TemplateInstance` since the model move, so a user asking to see or copy their
719
+ metadata got `_values` and `_iris` — CEE's internals. They now render what the
720
+ model library's writer produces. The panel that leaves the envelope off was
721
+ corrected earlier; this is the one that keeps it.
722
+
723
+ ## [1.6.0-dev.20260811.61ac9c2] - 2026-08-11
724
+
725
+ ### Fixed
726
+
727
+ - An attribute-value field stays editable after a host saves an instance and injects it
728
+ back. The model reader pairs the wire form's list of names with their sibling values
729
+ into an `InstanceDataAttributeValueField`, which reads well and is not a list; the
730
+ pager treated it as one and crashed on the first change-detection pass. A loaded
731
+ instance is brought to the typed list shape CEE creates and edits, once, at the input
732
+ boundary.
733
+
734
+ ## [1.6.0-dev.20260811.26630a1] - 2026-08-11
735
+
736
+ ### Fixed
737
+
738
+ - An attribute-value field keeps the name the user typed. The widget is handed a
739
+ one-entry name/value view the active-component registry projects from the instance,
740
+ and it guarded that view with the model library's `isInstanceObject`, which tests
741
+ for an `InstanceDataContainer`. A plain projected object is never one, so the guard
742
+ rejected every payload and cleared both controls on each sync. The guard now
743
+ describes the view the registry actually sends, and accepts a name whose value is
744
+ not filled in yet. The visual test that fills a name asserts the name is still
745
+ there afterwards.
746
+ - An open suggestion panel stays with its field while the host page scrolls, rather
747
+ than holding its original position over unrelated fields. It affected every
748
+ autocomplete — the seven authority fields and controlled terms — and every select.
749
+ Neither of Material's scroll strategies can reach this case: the default filters
750
+ scroll events to `cdkScrollable` ancestors, and the container CEE scrolls inside
751
+ belongs to the embedding page, while the close strategy listens on `document` in
752
+ the bubble phase, which a `scroll` event never reaches. The strategy here listens
753
+ in the capture phase and repositions from the origin's own rect. A scroll inside
754
+ the panel is ignored, so reading a long suggestion list does not move it.
755
+ - A populated authority field no longer offers its own value back as its one
756
+ suggestion, and focusing one sends no lookup for the compound `Label - iri` string.
757
+ The autocomplete is disabled while the box shows the term already chosen, and
758
+ re-enables on the first keystroke.
759
+ - A static image field is centred. `margin-left: auto` and `margin-right: auto` never
760
+ applied, because an `img` is inline and an inline element ignores auto margins;
761
+ `display: block` is what makes them mean anything.
762
+
763
+ ### Removed
764
+
765
+ - The khaki background declared on the image field's card. Material sets the card
766
+ background at a higher specificity, so no one has ever seen it — the card has
767
+ always been white, like every other field.
768
+
769
+ ### Fixed
770
+
771
+ - An attribute-value field the loaded instance carries no slot for can be filled in.
772
+ A template declares the property and an instance need not carry it, so a field
773
+ nobody has filled in arrives with nothing at that path. The add had no list to put
774
+ an occurrence into and turned the click away, leaving a field that could not be
775
+ used and reported it only in the console; the list the template implies is now
776
+ created. A node holding something other than a list is left alone. The "All
777
+ values" summary no longer reports the same absent node as an error on every
778
+ change-detection pass.
779
+
780
+ ### Changed
781
+
782
+ - Every screenshot is judged by an absolute pixel budget rather than a ratio of the
783
+ page, so a localised change to a tall page is no longer forgiven in proportion to
784
+ the page's height.
785
+ - Takes the model library build that inflates an omitted attribute-value field to an
786
+ empty list rather than an empty node, which is the other way a document reaches
787
+ CEE with nothing usable at that path.
788
+
789
+ ## [1.6.0-dev.20260810.ab37f62] - 2026-08-10
790
+
791
+ ### Changed
792
+
793
+ - CEE reads no key constant from the model library. It imported two — the name given to
794
+ an attribute whose name collides with another, which is CEE's own product decision and
795
+ now lives here, and the namespace property IRIs are minted in, which the library exposes
796
+ as `PropertyIri` along with both ways one is arrived at. Neither was a serialization key.
797
+ - Requires `@org.metadatacenter/cedar-model-typescript-library@0.9.2-dev.20260810.b48728a`,
798
+ which stops exporting `JsonSchema`, `YamlKeys` and `CedarModel`. Those are the spelling
799
+ tables its readers and writers use to describe a document; a consumer works in artifacts
800
+ and asks for a serialization by name, at the edge, from a writer.
801
+ - The external authority service no longer reads a response shape no authority sends. A
802
+ branch handled `results` arriving as a list of terms and named the keys such a term would
803
+ carry; it came from a guard in the widget one layer downstream, where the value being
804
+ tested was the service's own output. A response that is not the documented map now yields
805
+ no terms.
806
+
807
+ ## [1.6.0-dev.20260810.e4b63f4] - 2026-08-10
808
+
809
+ ### Changed
810
+
811
+ - The instance CEE edits is a model rather than a CEDAR document. `DataContext.instanceFullData`
812
+ holds a `TemplateInstance`, so CEE no longer writes the `@context` block, the nine envelope
813
+ keys or a minted `@id` per occurrence — how any of that is written down is asked once, at the
814
+ edge, by the model library's writer. Gone with it: the five value keys CEE kept in order to
815
+ clear a stale one when a field changed kind, the in-place value overwrite the widgets needed
816
+ while nodes had identity, the round trip that handed the library's reader the tree CEE had
817
+ just edited, and the projections that rebuilt a parsed model as plain objects.
818
+ - The data quality report hands a host page the instance as a document, under `instance`. It was
819
+ `instanceExtractData`, the envelope-free view of a tree that was itself a document; handing
820
+ out the model's container would have shown a host `_values` and `_iris`.
821
+ - Requires `@org.metadatacenter/cedar-model-typescript-library@0.9.2-dev.20260810.cc9ff84`, which
822
+ refuses to construct a value that is not a value or a controlled-term constraint that points at
823
+ nothing, and which lets a container be edited through its own methods rather than through the
824
+ two dictionaries it exposes for reading.
825
+
826
+ ### Fixed
827
+
828
+ - The source panel shows the instance as a document again. It rendered the working tree through
829
+ the `json` pipe, which showed a user their metadata while the tree was a document and CEE's
830
+ internals once it was not.
831
+ - An attribute-value slot added with the pager, or produced by copying an occurrence, stays
832
+ unnamed until the user names it, and an unnamed slot is left out of the pager's summary.
833
+
834
+ ## [1.6.0-dev.20260809.9755ad1] - 2026-08-09
835
+
836
+ ### Changed
837
+
838
+ - CEE no longer names a CEDAR serialization key outside its two wire adapters. The authority
839
+ layer — ORCID, ROR, and the five simpler authorities, plus the terminology server's integrated
840
+ search — held its terms as `{'@id', 'rdfs:label'}`, borrowing the model library's key constants
841
+ for HTTP responses that have nothing to do with CEDAR's JSON. Terms are now `{iri, label}`. The
842
+ three keys that remain are read where an external service sends them, and converted on arrival.
843
+ - Because those constants are declared `string` rather than as literals, an interface keyed by them
844
+ became an index signature over every string key. That is what made a `details` member untypeable
845
+ and forced 20 `as string` casts; all of it is gone.
846
+ - An attribute-value slot added with the pager, or produced by copying an occurrence, stays unnamed
847
+ until the user names it. It used to be given `Attribute Value Field1` on the next sync, so a user
848
+ who clicked "+" and stopped had a property in their instance they never asked for.
849
+
850
+ ### Fixed
851
+
852
+ - Static image and YouTube fields, temporal inputs, section breaks, radio controls, pager actions
853
+ and nested element headings all take the styling pass's corrections; the visual baselines they
854
+ left behind are re-recorded.
855
+
856
+ ## [1.6.0-dev.20260809.604e9e6] - 2026-08-09
857
+
858
+ ### Added
859
+
860
+ - Static image fields honour the width and height a template asks for. The previous release
861
+ recorded that they could not, because the model library carried `width` and `height` on its
862
+ YouTube field alone. `0.9.2-dev.20260808.92f3412` carries them on the image field too, and CEE
863
+ now reads them. A template declaring no size leaves the attributes unset, so the browser uses the
864
+ image's own dimensions, while a static YouTube field, having no intrinsic size of its own, still
865
+ falls back to 640 × 390.
866
+ - The instance-conformance spec runs in the domain harness. It builds CEE's instance for each
867
+ corpus template and validates it against that template with the model library's
868
+ `InstanceValidator`, so a dropped `@type` or a missing property fails the gate.
869
+ - `--cee-element-heading-size`, `--cee-element-heading-weight` and `--cee-element-content-gap` on
870
+ the element, so an embedder can adapt the typography and density of a nested element's heading
871
+ and content without acquiring a second say in the template's structure.
872
+
873
+ ### Changed
874
+
875
+ - The build runs through `@angular/build:application`. The webpack `browser` builder is gone,
876
+ along with the dev-server and extract-i18n builders beside it.
877
+ - `@angular-devkit/build-angular` is no longer a devDependency. Nothing had referenced it since the
878
+ builder move, and removing it takes 427 packages and eight `npm audit` findings with it, every
879
+ high among them. `npm run audit:prod`, which describes what an embedder downloads, reported 0
880
+ before and after.
881
+ - TypeScript `strict` is on throughout, including the domain harness, which previously opted out.
882
+ - The model library moves to `0.9.2-dev.20260808.92f3412` in the application, the harness and the
883
+ visual suite together, since a skew between them would mean the domain tests and the bundle
884
+ disagreed about the model.
885
+
886
+ ### Fixed
887
+
888
+ - `CeeDataQualityReport` names the problem array `problems`, as the report has always carried it.
889
+ The declarations called it `validationProblems`, so a TypeScript host reading that member
890
+ compiled and received `undefined`. Hosts using the published declarations should rename their
891
+ reads.
892
+ - `cee-public-api.spec.ts` holds the report types against the objects behind them. It checked the
893
+ configuration keys only, which is how the name drifted unnoticed.
894
+ - Read-only mode hides the multi-instance pager for a group holding one instance or none, rather
895
+ than showing a control that offers nothing.
896
+ - The attribute-value widget labels its value input `Generic.AttributeValue` instead of repeating
897
+ the name input's label, and floats both labels so neither collapses over a filled value.
898
+ - The multi-instance pager's actions align with its chips, and reflow beneath them below 620px
899
+ rather than overhanging the container.
900
+ - A numeric field renders its unit only when the template declares one, with spacing that keeps it
901
+ clear of the input.
902
+ - The page-break paginator drops the 64px of margin Material reserved for a range label the
903
+ component hides, and gives its arrows a 44px target.
904
+
905
+ ## [1.6.0-dev.20260809.8127503] - 2026-08-09
906
+
907
+ ### Added
908
+
909
+ - TypeScript declarations for the host contract, shipped with the package: `CeeConfig`,
910
+ `CedarEmbeddableEditorElement`, the report and event-handler types, and an
911
+ `HTMLElementTagNameMap` entry so `document.querySelector('cedar-embeddable-editor')` is
912
+ typed without a cast. Types only — the bundle registers a custom element and exports no
913
+ values, so use `import type`.
914
+ - Configuration is checked when it crosses the custom-element boundary. Unknown keys are
915
+ named with the nearest real key suggested, values of the wrong kind say what was
916
+ expected, and conflicting settings are reported. This covers the two routes a compiler
917
+ cannot: a JavaScript host, and `loadConfigFromURL`. Reporting only — a key CEE cannot
918
+ use is ignored as it always was.
919
+
920
+ ### Changed
921
+
922
+ - The version names the commit whose content it carries. `-ng22` named the branch that
923
+ produced the build and `-eN` counted local deploys from it; both stopped meaning
924
+ anything once that branch was merged and CEE went back to being developed on
925
+ `develop`. A published build is now `1.6.0-dev.<date>.<sha>`, which identifies exactly
926
+ one set of bytes and says where to read them.
927
+
928
+ - The temporal editors are rebuilt around one temporal value. Date, time and timezone
929
+ are parsed, normalized and rendered through `CedarTemporalValue` rather than each
930
+ picker carrying its own string handling, so a field's granularity decides what is
931
+ shown and what is written. The time and timezone pickers were reworked to match.
932
+ Existing values are normalized on load: information finer than the template's
933
+ declared granularity is intentionally discarded, hidden parts are padded to a
934
+ canonical complete XSD value, and an offset is removed when time zones are disabled.
935
+
936
+ - A typed field reports its validation error on blur rather than on every keystroke,
937
+ so an address, email, phone number or URL is not marked invalid while it is still
938
+ being typed.
939
+
940
+ - A static YouTube field renders at the size its template asks for. `_ui._size` was
941
+ read by nobody: the component carried 640 × 390 as two fixed values, so every video
942
+ was that size whatever the template said. The corpus asks for 400 × 300 six times
943
+ and 192 × 108 four times. A template that sets no size still gets 640 × 390, and a
944
+ dimension that cannot be a size — zero, negative, not a number — falls back on its
945
+ own rather than taking the other with it.
946
+
947
+ Static **images** still ignore `_ui._size`, and cannot honour it: the model library
948
+ models `width` and `height` on its YouTube field and not on its image field, so an
949
+ image's size is gone before CEE can see it.
950
+
951
+ - Templates use Angular's block control flow. All 203 `*ngIf` and `*ngFor` sites across
952
+ 33 templates are now `@if` and `@for`, migrated by
953
+ `ng generate @angular/core:control-flow`. The directives have been deprecated since
954
+ Angular 20 and are intended for removal in a later major. Nothing renders differently
955
+ — all 108 pixel snapshots match — and the bundle is 17,912 bytes smaller, since the
956
+ blocks compile to instructions rather than pulling the directives in.
957
+
958
+ ### Removed
959
+
960
+ - `BrowserAnimationsModule`, and with it the `@angular/animations` dependency. Angular
961
+ deprecated the module at 20.2 and intends to remove it at 23. CEE declares no
962
+ animation of its own, and Material 22 animates in CSS without that package, so
963
+ nothing changes visually — 64,099 bytes leave the bundle.
964
+
965
+ ### Fixed
966
+
967
+ - Timezone data is current. `moment-timezone` was pinned to a release carrying tzdb
968
+ 2023c, so the timezone picker computed offsets from rules three years old and
969
+ stamped them into instances. `Asia/Almaty` read `+06:00` after Kazakhstan unified
970
+ the country to UTC+5 in March 2024, and `America/Asuncion` read `-04:00` after
971
+ Paraguay abandoned daylight saving the same year. Now tzdb 2026c — and the bundle
972
+ is 60,655 bytes smaller, since the newer packed dataset is smaller despite
973
+ covering three more years.
974
+
975
+ ### Security
976
+
977
+ - Vitest moves from 1.6.1 to 4.1.10, in the root and the harness together, clearing
978
+ the critical advisory that lets a listening Vitest UI server read and execute
979
+ arbitrary files. Nothing shipped is affected: this is test tooling, and CEE never
980
+ had `@vitest/ui` installed. The exposure was one command away rather than present,
981
+ since the harness declared a `test:ui` script for a package that was not a
982
+ dependency — that script is removed. Both projects had to move at once: the harness
983
+ sets its Vite root to the repository, so a split loads the root's worker and dies
984
+ with `No handler function exported`. A root audit falls from 19 findings to 12, both
985
+ criticals among the seven, and the harness now reports none.
986
+
987
+ - `lodash-es` moves to 4.18.1, clearing the one high-severity advisory group a
988
+ production audit reported against 4.17.21 — code injection through `_.template`,
989
+ prototype pollution through `_.unset` and `_.omit`. CEE calls only `cloneDeep`, so
990
+ no advisory described a path this code could reach, but a flagged package is one
991
+ every embedder would otherwise have to reason about themselves. The shipped bundle
992
+ is byte-for-byte unchanged by the upgrade.
993
+ - Static rich-text fields are sanitized by default. A template author's markup previously
994
+ rendered verbatim, so an embedder that let its users choose a template gave those users
995
+ script execution in the embedder's origin — a property documented only in a source
996
+ comment. Script, event handlers, `javascript:` URLs, frames, form controls and AngularJS
997
+ directive attributes are removed; inline styles, tables, lists, links and raster `data:`
998
+ images are kept, so the formatting the field exists for is unaffected. Angular's own
999
+ sanitizer cannot do this: it drops the `style` attribute that 99 of the 271 static
1000
+ content blocks in the CEDAR, HuBMAP and test-artifact corpora carry.
1001
+ - Added the `trustTemplateMarkup` configuration key, default `false`, for hosts that
1002
+ control which templates load and want the author's markup rendered as written. The
1003
+ README's new _Embedding security_ section says who should set it and who should not.
1004
+ - Links in template rich text that open a new tab are given `rel="noopener noreferrer"`.
1005
+
1006
+ ### Fixed
1007
+
1008
+ - A static rich-text field's body renders in a `div` rather than a `p`. Rich text is block
1009
+ content, which a `p` cannot contain, so the browser was silently reparenting it.
1010
+
1011
+ ## [1.6.0-dev.20260806.62725e3] - 2026-08-06
1012
+
1013
+ ### Fixed
1014
+
1015
+ - Static image fields report a URL that cannot be loaded instead of rendering an empty card, and
1016
+ fall back to the field label when `schema:description` is empty rather than emitting `alt=""`.
1017
+ - Static YouTube fields explain why a link cannot be embedded — a playlist or channel link, a
1018
+ non-YouTube host, or an invalid video ID — instead of rendering an empty card.
1019
+ - Controlled-term and external-authority fields distinguish a failed lookup from one that matched
1020
+ nothing, rather than labelling both "No results found".
1021
+ - A failed terminology lookup no longer ends the controlled-term field's `valueChanges` pipeline,
1022
+ which left its autocomplete inoperative for the rest of the session.
1023
+
1024
+ ## [1.6.0-dev.20260804.85b7ccf] - 2026-08-04
1025
+
1026
+ ### Added
1027
+
1028
+ - JSON and YAML instance serialization through the CEDAR Model TypeScript Library.
1029
+ - Browser, domain, and multiple-editor isolation regression coverage.
1030
+
1031
+ ### Changed
1032
+
1033
+ - Isolated each editor's configurable services, endpoints, language settings, preferences, and IRI prefixes.
1034
+ - Encapsulated CEE and Angular Material styles in the custom element's shadow root.
1035
+
1036
+ ### Fixed
1037
+
1038
+ - Released destroyed UI component registrations and shadow-local overlay and accessibility nodes.
1039
+
8
1040
  ## [1.5.2] - 2026-07-28
9
1041
 
10
1042
  ### Added