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