@zambon-dev/ui 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +2329 -0
  2. package/README.md +80 -2
  3. package/fesm2022/zambon-dev-ui-framework.mjs +5960 -0
  4. package/fesm2022/zambon-dev-ui-framework.mjs.map +1 -0
  5. package/fesm2022/zambon-dev-ui-library.mjs +5795 -0
  6. package/fesm2022/zambon-dev-ui-library.mjs.map +1 -0
  7. package/fesm2022/zambon-dev-ui-shared.mjs +2517 -0
  8. package/fesm2022/zambon-dev-ui-shared.mjs.map +1 -0
  9. package/fesm2022/zambon-dev-ui-testing.mjs +76 -0
  10. package/fesm2022/zambon-dev-ui-testing.mjs.map +1 -0
  11. package/fesm2022/zambon-dev-ui.mjs +3 -0
  12. package/i18n/framework/en.json +63 -0
  13. package/i18n/framework/pt.json +63 -0
  14. package/i18n/shared/external-content/en.json +12 -0
  15. package/i18n/shared/external-content/pt.json +12 -0
  16. package/i18n/shared/language-selector/en.json +5 -0
  17. package/i18n/shared/language-selector/pt.json +5 -0
  18. package/i18n/shared/login/en.json +14 -0
  19. package/i18n/shared/login/pt.json +14 -0
  20. package/i18n/shared/operations-history/en.json +5 -0
  21. package/i18n/shared/operations-history/pt.json +5 -0
  22. package/i18n/shared/services-history/en.json +14 -0
  23. package/i18n/shared/services-history/pt.json +14 -0
  24. package/i18n/shared/top-bar/en.json +7 -0
  25. package/i18n/shared/top-bar/pt.json +7 -0
  26. package/package.json +106 -4
  27. package/styles/common.scss +10 -0
  28. package/styles/components/button.scss +101 -0
  29. package/styles/components/card.scss +13 -0
  30. package/styles/components/dropdown-static.scss +35 -0
  31. package/styles/components/input-group.scss +44 -0
  32. package/styles/components/input.scss +99 -0
  33. package/styles/components/label.scss +3 -0
  34. package/styles/components/row.scss +48 -0
  35. package/styles/mixins/background.scss +31 -0
  36. package/styles/theme.css +75 -0
  37. package/styles/variables.scss +60 -0
  38. package/types/zambon-dev-ui-framework.d.ts +1496 -0
  39. package/types/zambon-dev-ui-library.d.ts +1675 -0
  40. package/types/zambon-dev-ui-shared.d.ts +675 -0
  41. package/types/zambon-dev-ui-testing.d.ts +51 -0
  42. package/types/zambon-dev-ui.d.ts +3 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,2329 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@zambon-dev/ui` are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ > **Upgrading?** Each release lists what changed and, under
9
+ > **⚠ Breaking Changes / Migration**, the concrete steps required to move to that version.
10
+ > When no migration is needed, that subsection is omitted.
11
+
12
+ ## [Unreleased]
13
+
14
+ ### Added
15
+
16
+ ### Changed
17
+
18
+ ### Deprecated
19
+
20
+ ### Removed
21
+
22
+ ### Fixed
23
+
24
+ ### ⚠ Breaking Changes / Migration
25
+
26
+ ## [1.0.0] - 2026-10-24
27
+
28
+ ### ⚠ Breaking Changes / Migration
29
+
30
+ - **The layers in this release are `library` 4.0.5, `framework` 5.0.5 and `shared` 6.0.4.**
31
+ Moving here from an earlier version of any of the three means taking that layer's remaining
32
+ releases too; they are under **Package history** below. The one worth naming is
33
+ `ModalComponent.isShown`, which became `isOpen` and is a `Signal<boolean>`: reading the
34
+ signal object itself is valid TypeScript and always truthy, so the build will not catch a miss.
35
+ Grep for `isShown` and `isModalShown`.
36
+
37
+ - **This package replaces `@zambon-dev/library`, `@zambon-dev/framework` and
38
+ `@zambon-dev/shared`.** Install `@zambon-dev/ui` in place of all three and rewrite the
39
+ imports:
40
+
41
+ | Was | Now |
42
+ | ----------------------------- | -------------------------- |
43
+ | `@zambon-dev/library` | `@zambon-dev/ui/library` |
44
+ | `@zambon-dev/framework` | `@zambon-dev/ui/framework` |
45
+ | `@zambon-dev/shared` | `@zambon-dev/ui/shared` |
46
+ | `@zambon-dev/library/testing` | `@zambon-dev/ui/testing` |
47
+
48
+ `@zambon-dev/ui` also re-exports all three layers, so a single import works where the layer
49
+ does not matter. See [the migration guide](https://github.com/RicardoZambon/ZLibraries/blob/main/docs/guides/migrating-to-ui.md).
50
+
51
+ - **Stylesheet subpaths name their file.** `@zambon-dev/library/styles` and
52
+ `@zambon-dev/shared/styles` both existed and resolved to different stylesheets, so the bare
53
+ subpath is gone: use `@zambon-dev/ui/styles/variables.scss`,
54
+ `@zambon-dev/ui/styles/common.scss` or `@zambon-dev/ui/styles/theme.css`.
55
+
56
+ - **Translation assets moved under a per-layer folder**, to `@zambon-dev/ui/i18n/framework` and
57
+ `@zambon-dev/ui/i18n/shared`. Applications that spread `ZAMBON_FRAMEWORK_I18N_ASSET` or
58
+ `ZAMBON_SHARED_I18N_ASSET` into their build assets need no change — the constants carry the
59
+ new path. A hand-written asset entry does.
60
+
61
+ - **The peer set is the union of the three packages'**, so `@angular/cdk`,
62
+ `@angular/platform-browser`, `@auth0/angular-jwt`, `@microsoft/signalr`,
63
+ `ngx-resize-observer` and `ngx-translate-multi-http-loader` are all required. Every
64
+ application consuming these packages already installs them.
65
+
66
+ - **`AngularAssetPattern` is exported once**, from the library layer, rather than declared
67
+ separately by each i18n helper. Import it from `@zambon-dev/ui` or
68
+ `@zambon-dev/ui/library`; the framework and shared layers no longer export it.
69
+
70
+ ## Package history
71
+
72
+ The three packages this one replaces. Their releases stay installable from npm; nothing further
73
+ will be published on them.
74
+
75
+ ### Library
76
+
77
+ UI components, directives, pipes and the data-grid services. Published as `@zambon-dev/library` through 4.0.5.
78
+
79
+ #### [4.0.5](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v4.0.5) - 2026-10-24
80
+
81
+ ##### Changed
82
+
83
+ - **A grid row's checkbox follows the grid's selection by being told, not by forcing a pass.**
84
+ `DataGridRowComponent` held its selected state in a plain field and called
85
+ `ChangeDetectorRef.detectChanges()` on itself whenever the grid announced a change. That
86
+ rendered -- there is no behaviour change here -- but it rendered because the component forced
87
+ a pass rather than because anything was notified, which is the one thing this package's
88
+ components are not supposed to need. The state is a signal now and the `ChangeDetectorRef` is
89
+ gone; it was the last one in the package.
90
+
91
+ #### [4.0.4](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v4.0.4) - 2026-10-24
92
+
93
+ ##### Changed
94
+
95
+ - **`DataProviderService` announces the entity it holds.** The identifier behind `entityID`
96
+ and `hasEntityID` is a signal now. It is read from templates -- `@zambon-dev/framework`'s New,
97
+ Edit and Views buttons all appear only once a record exists -- and the route resolving or a
98
+ save assigning an id told Angular nothing. Both accessors keep their shape, so no call site
99
+ changes.
100
+
101
+ #### [4.0.3](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v4.0.3) - 2026-10-24
102
+
103
+ ##### Changed
104
+
105
+ - **Every component in this package now renders under any change-detection strategy the host
106
+ application uses, including zoneless.** `CatalogSelectComponent` was the last one. Nothing here
107
+ depends on being walked on every event, and
108
+ `@angular-eslint/prefer-on-push-component-change-detection` is an error in this package rather
109
+ than a warning, so it stays that way.
110
+
111
+ ##### ⚠ Breaking Changes / Migration
112
+
113
+ - **`CatalogSelectComponent` exposes its inputs as signals.** Binding to it from a template is
114
+ unchanged -- `[label]="..."`, `[searchEndpoint]="..."`, `[filters]="..."` and the rest all
115
+ still work. Only code reading one off a component instance needs `()`. `controlName` and
116
+ `displayControlName` are required now, which they always were in practice.
117
+
118
+ `entriesList`, `filters` and `searchEndpoint` stay plain inputs. They are imperative
119
+ configuration that re-initialises the search, no template reads them, and making them signals
120
+ would have moved that side effect into an `effect()` for no gain.
121
+
122
+ #### [4.0.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v4.0.2) - 2026-10-24
123
+
124
+ - Maintenance release (no consumer-facing changes were documented).
125
+
126
+ #### [4.0.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v4.0.1) - 2026-10-24
127
+
128
+ ##### Changed
129
+
130
+ - **`FormService` and the datasets announce their own changes.** A component reading
131
+ `formService.loading`, `formService.model`, `formService.isEditMode`, `formService.isValid`,
132
+ `dataGridDataset.selectedRowKeys`, `resultDataset.displayedRows` or a grid's loading state from
133
+ its template now re-renders when that state changes, whatever strategy the host application
134
+ uses. `columns` is deliberately left alone: a concrete dataset declares it once and never
135
+ changes it, so making it reactive would only have broken every subclass that declares it.
136
+ **Nothing to do on your side** -- these were already accessors, or became accessors of the same
137
+ shape, so every read and write keeps working. `isValid` also
138
+ mirrors the `FormGroup`'s status, which is not a signal and never notified one, so a control
139
+ bound to it no longer keeps its first answer.
140
+
141
+ - **`RibbonButtonComponent`, `FormInputComponent` and `DataGridComponent` render under any
142
+ change-detection strategy.** Their inputs are signals, and none of them depends on being walked
143
+ on every event any more. The grid also stops calling `ChangeDetectorRef.detectChanges()` and
144
+ stops re-entering the Angular zone to publish a measurement -- a signal write reaches the
145
+ screen on its own.
146
+
147
+ - **Seven more components render under any change-detection strategy**, continuing the work the last
148
+ major started: `ModalComponent`, `GroupAccordionComponent`, `GroupScrollSpyComponent`,
149
+ `MultiSelectComponent`, `MultiEditorComponent`, `FormInputGroupComponent` and
150
+ `MultiSelectResultGridComponent`. Their state is signals now, and none of them declares a
151
+ strategy. Four library components are still to go; they are named in
152
+ `change-detection-contract.spec.ts`.
153
+
154
+ ##### ⚠ Breaking Changes / Migration
155
+
156
+ - **`ModalComponent.isShown` is now `isOpen`, and it is a `Signal<boolean>`.** The rename is
157
+ deliberate: a signal keeping the old name would leave `if (modal.isShown)` compiling and
158
+ permanently true. Replace `modal.isShown` with `modal.isOpen()`.
159
+
160
+ - **`IModal` changed with it.** It declares `readonly isOpen: Signal<boolean>` instead of
161
+ `get isShown(): boolean`, and no longer declares `size` or `title` -- those are presentational
162
+ inputs each component declares for itself, and nothing drove a modal through them. If you
163
+ implement `IModal` by wrapping our modal, delegate with a signal view query:
164
+
165
+ ```ts
166
+ private readonly modal = viewChild(ModalComponent);
167
+ public readonly isOpen: Signal<boolean> = computed(() => this.modal()?.isOpen() ?? false);
168
+ ```
169
+
170
+ A `computed` reading a plain `@ViewChild` never re-runs when the query resolves, so the wrapper
171
+ would answer against an undefined child for ever.
172
+
173
+ - **`ModalComponent`'s inputs are signals.** Reading one in your own template needs `()`:
174
+ `title()`, `size()`, `position()`, `dialog()`, `closeButtonText()`, `modalProcessing()`. Binding
175
+ to them from a template is unchanged. `closeButtonText` and `modalProcessing` are `model()`, so
176
+ code that sets them keeps working through `.set(...)`; the other four are `input()` and can only
177
+ be bound.
178
+
179
+ - **`GroupAccordionComponent.label` is a required signal input**, and `maxHeight` is a signal.
180
+ `GroupScrollSpyComponent` exposes `titles`, `activeSection`, `sections` and `scrollSpy` as
181
+ signals. `MultiSelectComponent` and `MultiEditorComponent` expose their inputs as signals --
182
+ `itemTemplate()`, `formGroup()`, `addButtonLabel()`, `removeButtonLabel()`, `showAddButton()`
183
+ and `showDeleteButton()`.
184
+
185
+ - **`RibbonButtonComponent`, `FormInputComponent` and `DataGridComponent` expose their inputs as
186
+ signals.** Binding to them from a template is unchanged. Only code reading one off an instance needs `()` --
187
+ `button.loading()` rather than `button.loading`.
188
+
189
+ Two are `model()` rather than `input()` because the components write them as well:
190
+ `RibbonButtonComponent.loading`, which `BaseButton` drives through `startLoading` and
191
+ `finishLoading`, and `FormInputComponent.fixedValue`, which is two-way bound through `ngModel`.
192
+ `FormInputComponent.format` is also a `model()`, since it defaults itself for date types when
193
+ the consumer supplies none.
194
+
195
+ `RibbonButtonComponent.action` is an `output()`, which has no no-argument overload, so emitting
196
+ "no option chosen" is now an explicit `emit(undefined)`.
197
+
198
+ - **`FormInputGroupComponent` and `MultiSelectResultGridComponent` expose their inputs as signals
199
+ too.** Binding to them from a template is unchanged -- `[label]="..."`, `[type]="..."`,
200
+ `[validations]="..."` and the rest all still work. Only code reading one off a component
201
+ instance needs `()`. `controlName` is required now, which it always was in practice.
202
+
203
+ #### [4.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v4.0.0) - 2026-10-24
204
+
205
+ ##### Added
206
+
207
+ - **A test harness that catches a component which silently stops rendering**, published as
208
+ `@zambon-dev/library/testing`. `renderUnderOnPush(MyComponent)` renders a component beneath a
209
+ clean OnPush ancestor -- which is what your application is -- and `render.tick()` runs a pass
210
+ that such an ancestor can still short-circuit. A component that updates state without telling
211
+ Angular cannot update its DOM under it, which is the failure a plain `TestBed` fixture cannot
212
+ express. Use it on your own components as you remove `ChangeDetectionStrategy.Eager` from them.
213
+ It works under both a zone and a zoneless test environment, so you can adopt it without
214
+ changing your test setup first.
215
+
216
+ ##### Fixed
217
+
218
+ - **The sidebar fills in on its own again.** In an application whose components use Angular 22's
219
+ default strategy -- which is now OnPush -- the sidebar rendered its loading spinner and only
220
+ filled in when the user next clicked something. Its menus arrived from HTTP, were assigned to a
221
+ field, and nothing told Angular to render. `SidebarService` and `SidebarComponent` now hold that
222
+ state in signals, so it reaches the screen whatever strategy your application uses.
223
+ - **A sidebar item's selected state reaches its host element.** `[class.selected]` is a host
224
+ binding, which Angular evaluates during the _parent's_ pass, so the component's own
225
+ `detectChanges()` could never refresh it and writing it afterwards raised NG0100.
226
+ - **A ribbon group hides and shows itself when its buttons' visibility resolves.** The group reads
227
+ its children's visibility from a host binding; nothing notified it when an asynchronous access
228
+ check flipped one.
229
+
230
+ ##### ⚠ Breaking Changes / Migration
231
+
232
+ - **`RibbonGroupChild.visible` is now `isVisible`, and it is a `Signal<boolean>`.** If you
233
+ implement `RibbonGroupChild` yourself -- a custom ribbon button -- replace the field:
234
+
235
+ ```ts
236
+ // before
237
+ public visible = true;
238
+ // after
239
+ public readonly isVisible: Signal<boolean> = computed(() => ...); // or signal(true)
240
+ ```
241
+
242
+ A plain field cannot work here: the group reads it from a host binding, and nothing can notify
243
+ the group when a field changes.
244
+
245
+ - **`RibbonGroupComponent.children` is a signal query**, `Signal<readonly RibbonGroupChild[]>`
246
+ instead of `QueryList`. Replace `children.length` with `children().length` and
247
+ `children.get(0)` with `children()[0]`.
248
+
249
+ - **`SidebarService.isActive`, `isCollapsed` and `shouldActivate` are accessors over signals.**
250
+ Reading and writing them is unchanged. But if your own `SidebarService` subclass **redeclares**
251
+ any of them as a field -- `public override isActive = false;` -- delete that line. A field
252
+ shadows the accessor, and the sidebar stops reacting to it.
253
+
254
+ #### [3.2.5](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.2.5) - 2026-10-22
255
+
256
+ ##### Fixed
257
+
258
+ - **A list reopened from the menu no longer comes back empty.** Opening a record from a list that
259
+ had been scrolled, then reopening that list from the menu, showed the column headers over an empty
260
+ body. The tab reuses the list's cached view, as it should, but while that view sat detached the
261
+ grid kept measuring its CDK viewport: the body leaving the document is a resize, and a load that
262
+ finishes after the user has left is followed by a re-measure too. Measured detached, the viewport
263
+ worked out its range from a scroll offset of 0 and left its own change-detection flag stuck, so
264
+ once reattached it computed the right range but could not write the content offset out — the rows
265
+ were rendered, translated to the top, while the viewport showed where the user had scrolled to.
266
+ Whether something later rescued it was a matter of timing, which is why it came and went. A
267
+ detached grid now measures nothing; coming back is a resize as well, so it measures on the way in.
268
+
269
+ #### [3.2.4](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.2.4) - 2026-10-22
270
+
271
+ ##### Fixed
272
+
273
+ - **A submit button inside a modal no longer reloads the whole application.** Confirming a filter
274
+ applied it and then, about a second later, reloaded the page. The modal's content sits in a native
275
+ `<form>`, and a `type="submit"` button — `framework-button-filters`' Confirm, or any `<button>`
276
+ without a type — made the browser submit it: a GET to the current URL, leaving a bare `?` behind.
277
+ Nothing in the modal ever cancelled that; the consumer's `FormGroupDirective` did, by accident,
278
+ for as long as the submit bubbled through it. Since 3.2.1 moved the modal's host to `<body>` it no
279
+ longer does, so the form now prevents its own native submission. The event still bubbles.
280
+
281
+ #### [3.2.3](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.2.3) - 2026-09-26
282
+
283
+ ##### Fixed
284
+
285
+ - **A ribbon button's options are no longer cut off by whatever the button sits in.** The list was
286
+ absolutely positioned inside the button, which works only while nothing between it and the page
287
+ clips. `lib-data-grid` does: it is `overflow: hidden`, its buttons bar is its first child, and a
288
+ child list's grid is only a few rows tall -- so the export button's options opened downwards into
289
+ the grid and lost their lower part, measured at 129px of list against 65px of room. The list now
290
+ renders in a CDK overlay attached to the document, as `lib-catalog-select` already did, and the
291
+ overlay also flips it above the button when there is no room below. The ribbon no longer needs
292
+ its own rule to pin a right-hand button’s list to the right, since the overlay works that out.
293
+
294
+ #### [3.2.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.2.2) - 2026-09-26
295
+
296
+ ##### Fixed
297
+
298
+ - **Component styles are compiled again.** The Tailwind 4 migration left every component's own
299
+ stylesheet unprocessed in the published bundle, so its `@apply` rules reached the browser as an
300
+ at-rule the browser does not know and it dropped every rule that used one. In practice the
301
+ components rendered unstyled in any application installing 3.2.1. ng-packagr resolves its
302
+ PostCSS configuration from the library directory rather than from the workspace root, and the
303
+ migration added one only at the root. Nothing failed along the way: Storybook compiles from
304
+ source and never sees the packaged artifact, which is why `tools/check-packaged-styles.mjs` now
305
+ runs against that artifact in CI and again before every publish.
306
+
307
+ #### [3.2.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.2.1) - 2026-09-26
308
+
309
+ ##### Fixed
310
+
311
+ - **Dialogs no longer open underneath the navigation.** `lib-modal` is `fixed` at `z-index: 50`,
312
+ which should put it over everything, but z-index only ranks siblings inside one stacking context
313
+ and the tab panel these views render in is its own. Inside it the 50 counted for nothing against
314
+ the sidebar outside it, and the tab strip stayed visible and clickable behind the dialog.
315
+
316
+ The modal now moves its host to `<body>` on init, which is the one place with nothing above it.
317
+ This covers every dialog built on it: multi-editor, multi-select, the filter panels, the confirm
318
+ and error modals, and the operations-history detail.
319
+
320
+ #### [3.2.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.2.0) - 2026-09-25
321
+
322
+ ##### Changed
323
+
324
+ - **Tailwind CSS 4.** The component styles in this package are compiled against Tailwind 4 now.
325
+ They are expanded at build time, so nothing in the published bundle changed shape -- but the
326
+ application consuming it has to be on Tailwind 4 as well.
327
+
328
+ #### [3.1.7](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.1.7) - 2026-09-25
329
+
330
+ ##### Fixed
331
+
332
+ - **A menu with children no longer runs its label under the expand chevron.** The chevron and the
333
+ external-link glyph sit in an absolute slot at the row's right edge, outside the anchor's flow,
334
+ so nothing reserved room for them: "Fluxo de delegação" overlapped the chevron by 11px. The row
335
+ reserves the slot now and the anchor fills what is left, so a long label ellipsises before it.
336
+
337
+ Rows with one of those glyphs give up the fixed anchor width to do this, so their label reflows
338
+ while the rail collapses instead of sliding out of view. Only those rows, only during the
339
+ animation.
340
+
341
+ - **`lib-data-grid` and `lib-group-container` follow their container's corner.** Both were a flat
342
+ `rounded-lg`, which inside the tab panel put an 8px corner 9px in from a 20px one — close enough
343
+ to look like a mistake rather than a choice. They read `var(--surface-radius, 0.5rem)` now: the
344
+ panel publishes the radius that keeps the two parallel, and everywhere else the 0.5rem default
345
+ is exactly what they had.
346
+
347
+ #### [3.1.6](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.1.6) - 2026-09-25
348
+
349
+ ##### Fixed
350
+
351
+ - **Icons drawn in CSS survive a FontAwesome major.** Four pseudo-elements set
352
+ `font-family: 'Font Awesome 6 Free'` literally: the sidebar item's expand/collapse chevron and
353
+ its external-link marker, and the checked-checkbox tick in `form-input` and in the published
354
+ `styles/components/input.scss`. FontAwesome 7 renames the family to `Font Awesome 7 Free` and,
355
+ unlike the FontAwesome 5 names, does not keep a 6 alias — so on an application that had moved to
356
+ v7 the family stopped resolving and every one of those glyphs rendered as a fallback box.
357
+
358
+ They now read `var(--fa-family-classic, 'Font Awesome 6 Free')`. FontAwesome 7 defines that
359
+ variable; 6 does not, so it takes the fallback. The same stylesheet is correct on both, and on
360
+ 8 as well as long as FontAwesome keeps the variable.
361
+
362
+ #### [3.1.5](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.1.5) - 2026-09-24
363
+
364
+ ##### Fixed
365
+
366
+ - **Pressing Enter in the multi-select search no longer saves the changes.** The search box sits
367
+ inside the modal's form, so Enter submitted it and the confirmation ran alongside the search --
368
+ committing a selection the user was still building. Enter now searches and nothing else.
369
+
370
+ - **The remove button in the multi-select panel works for an entry the search never showed.** It
371
+ removed by deselecting the row in the grid on the left, which knows nothing about a row it has
372
+ not loaded, so clicking the cross on an entry further down the list did nothing. The panel now
373
+ tells the dataset directly, and still deselects the grid row when there is one.
374
+
375
+ - **Grid checkboxes are no longer drawn as disabled.** The disabled attribute was bound to the
376
+ boolean itself, and an attribute binding writes `disabled="false"` for false -- which disables a
377
+ control just as firmly as any other value. Every checkbox rendered faded and unusable, leaving
378
+ the row label as the only way to toggle a selection.
379
+
380
+ - **A multi-select or multi-editor no longer adds a gap below the page.** Both render nothing but
381
+ a fixed-position modal, yet their host still counted as a child in the parent's layout, so a
382
+ flex column with a gap reserved a slot for it under the last visible element.
383
+
384
+ **Requires Tailwind CSS 4**, and `@zambon-dev/shared` 5.x, which ships the theme. The migration
385
+ steps are in the `@zambon-dev/shared` changelog for this release; there is nothing specific to
386
+ this package to do beyond following them.
387
+
388
+ #### [3.1.4](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.1.4) - 2026-09-23
389
+
390
+ ##### Fixed
391
+
392
+ - **A `lib-catalog-select` with `searchable` off no longer looks disabled.** It was drawn on the
393
+ same grey as a disabled field, so nothing distinguished a picker you are meant to click from a
394
+ control that is not yours to touch.
395
+
396
+ Turning searching off makes the inner input read-only to the keyboard, and `lib-form-input`
397
+ paints a read-only input as disabled by default. That default is right for `readOnly`, which
398
+ means the value cannot be changed at all; it is wrong here, where only typing is withheld. The
399
+ disabled appearance is now tied to `readOnly` alone.
400
+
401
+ #### [3.1.3](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.1.3) - 2026-09-23
402
+
403
+ ##### Added
404
+
405
+ - **`lib-catalog-select` takes a `searchable` input.** A catalog of one or two entries is a picker,
406
+ not a search: the text box invites typing that only gets in the way, and the minimum-length rule
407
+ hides the very entries the user opened the field to choose from. Setting `[searchable]="false"`
408
+ leaves the input read-only to the keyboard while the dropdown still opens on click and the clear
409
+ button still works.
410
+
411
+ It differs from `readOnly`, which means the value cannot be changed at all and takes the clear
412
+ button away. It does not suit an endpoint that answers `shouldUseCriteria`: that endpoint
413
+ returns nothing until it is given criteria, and there would be no way left to type any.
414
+
415
+ - **`lib-catalog-select` exposes `refresh()`.** The entries are fetched once, when the field
416
+ initialises, so a screen that changes the data behind the catalog -- granting a role, moving an
417
+ employee -- kept offering the list it read before the change. Calling `refresh()` re-reads the
418
+ catalog, including whether its endpoint now requires search criteria.
419
+
420
+ ##### Fixed
421
+
422
+ - **`lib-catalog-select` no longer reports "no results" when its endpoint arrives after the field
423
+ does.** A screen that reads the endpoint off a model it loads over HTTP binds an empty string
424
+ first. The field initialised against that, showed _no results found_ on the first open, and only
425
+ filled in once the user typed something and cleared it again.
426
+
427
+ With no endpoint the component takes its static-list path, finds an empty list and latches the
428
+ message. `searchEndpoint` was a plain input, so nothing re-ran the search when the real value
429
+ landed -- unlike `entriesList` and `filters`, which have always refreshed on change. It is now a
430
+ setter and re-reads the catalog, treating a new endpoint as a new catalog rather than carrying
431
+ over what the previous one reported about needing search criteria.
432
+
433
+ #### [3.1.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.1.2) - 2026-09-22
434
+
435
+ ##### Fixed
436
+
437
+ - **Group headings in a narrow navigation panel no longer spill their label past the edge.** On a
438
+ window under 768px the panel opens at its collapsed width, but each group still printed its full
439
+ label -- clipped mid-word against a bar with no room for it -- where it should show the short
440
+ divider the collapsed panel uses.
441
+
442
+ The width that widens the panel is declared inside the `md` query, so below that breakpoint the
443
+ panel stays narrow whatever its expanded class says. The heading, though, was keyed off that
444
+ class rather than the width it actually got. It now follows the width, with the overlay still
445
+ excluded -- that one does widen the panel at any size.
446
+
447
+ #### [3.1.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.1.1) - 2026-09-22
448
+
449
+ ##### Fixed
450
+
451
+ - **`lib-catalog-select` now offers its clear button for a value it did not see arrive.** A field
452
+ restored from a saved filter showed the value and its label but no way to empty it, so the only
453
+ escape was to clear the whole filter and start again.
454
+
455
+ The button asked an internal field that is written when the component itself takes the value --
456
+ a pick in the dropdown, or the initial read in `ngOnInit`. A form patched after the component
457
+ has initialised, and patched without an event, reaches the form control and the display and
458
+ nothing else. The button now asks the form control, which is the value that actually counts.
459
+
460
+ #### [3.1.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.1.0) - 2026-09-21
461
+
462
+ ##### Added
463
+
464
+ - Added the --tabs-* design tokens to the published stylesheet. They style @zambon-dev/framework's
465
+ tab strip; they live here because this package owns the shared token layer, alongside --sidebar-*.
466
+
467
+ #### [3.0.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.0.1) - 2026-09-21
468
+
469
+ - Maintenance release (no consumer-facing changes were documented).
470
+
471
+ #### [3.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v3.0.0) - 2026-09-21
472
+
473
+ - Maintenance release (no consumer-facing changes were documented).
474
+
475
+ #### [2.0.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v2.0.1) - 2026-09-21
476
+
477
+ ##### Changed
478
+
479
+ - `RibbonComponent`, `FormGroupComponent` and `GroupContainerComponent` now use
480
+ `ChangeDetectionStrategy.OnPush`. They render from their inputs only, so behaviour is
481
+ unchanged, but consuming applications no longer check them on every change detection pass.
482
+
483
+ #### [2.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v2.0.0) - 2026-09-21
484
+
485
+ ##### Changed
486
+
487
+ - **Accessibility.** Every interactive element that previously responded only to a mouse is now
488
+ reachable and operable by keyboard. `RibbonButtonComponent` (button, dropdown toggle and each
489
+ option), `GroupAccordionComponent`, `GroupScrollSpyComponent`, `SidebarItemComponent` and the
490
+ `MultiSelectComponent` result grid gained focus, `Enter`/`Space` activation and the matching ARIA
491
+ roles and state. `CatalogSelectComponent` status rows are now `role="presentation"`; its retry
492
+ action is a real button.
493
+ - `MultiEditorComponent` and the multi-select result grid render their actions as `<button>`
494
+ rather than `<a>`. They are styled identically but now honour `disabled` natively.
495
+
496
+ > **The 1.6.x line is retired.** It carried the library for applications still on Angular 19 while
497
+ > `main` moved to Angular 22, publishing under its own `1.6.x` dist-tag. Its branch is gone; the
498
+ > final state is kept in the `angular-19/library-1.6.x` tag. Every fix recorded below also shipped
499
+ > in a 3.x release, which requires Angular 22.
500
+
501
+ #### [1.6.9](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.9) - 2026-09-24
502
+
503
+ ##### Fixed
504
+
505
+ - **Pressing Enter in the multi-select search no longer saves the changes.** The search box sits
506
+ inside the modal's form, so Enter submitted it and the confirmation ran alongside the search --
507
+ committing a selection the user was still building. Enter now searches and nothing else.
508
+
509
+ - **The remove button in the multi-select panel works for an entry the search never showed.** It
510
+ removed by deselecting the row in the grid on the left, which knows nothing about a row it has
511
+ not loaded, so clicking the cross on an entry further down the list did nothing. The panel now
512
+ tells the dataset directly, and still deselects the grid row when there is one.
513
+
514
+ - **Grid checkboxes are no longer drawn as disabled.** The disabled attribute was bound to the
515
+ boolean itself, and an attribute binding writes `disabled="false"` for false -- which disables a
516
+ control just as firmly as any other value. Every checkbox rendered faded and unusable, leaving
517
+ the row label as the only way to toggle a selection.
518
+
519
+ - **A multi-select or multi-editor no longer adds a gap below the page.** Both render nothing but
520
+ a fixed-position modal, yet their host still counted as a child in the parent's layout, so a
521
+ flex column with a gap reserved a slot for it under the last visible element.
522
+
523
+ #### [1.6.8](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.8) - 2026-09-23
524
+
525
+ ##### Fixed
526
+
527
+ - **A `lib-catalog-select` with `searchable` off no longer looks disabled.** It was drawn on the
528
+ same grey as a disabled field, so nothing distinguished a picker you are meant to click from a
529
+ control that is not yours to touch.
530
+
531
+ Turning searching off makes the inner input read-only to the keyboard, and `lib-form-input`
532
+ paints a read-only input as disabled by default. That default is right for `readOnly`, which
533
+ means the value cannot be changed at all; it is wrong here, where only typing is withheld. The
534
+ disabled appearance is now tied to `readOnly` alone.
535
+
536
+ #### [1.6.7](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.7) - 2026-09-23
537
+
538
+ ##### Added
539
+
540
+ - **`lib-catalog-select` takes a `searchable` input.** A catalog of one or two entries is a picker,
541
+ not a search: the text box invites typing that only gets in the way, and the minimum-length rule
542
+ hides the very entries the user opened the field to choose from. Setting `[searchable]="false"`
543
+ leaves the input read-only to the keyboard while the dropdown still opens on click and the clear
544
+ button still works.
545
+
546
+ It differs from `readOnly`, which means the value cannot be changed at all and takes the clear
547
+ button away. It does not suit an endpoint that answers `shouldUseCriteria`: that endpoint
548
+ returns nothing until it is given criteria, and there would be no way left to type any.
549
+
550
+ - **`lib-catalog-select` exposes `refresh()`.** The entries are fetched once, when the field
551
+ initialises, so a screen that changes the data behind the catalog -- granting a role, moving an
552
+ employee -- kept offering the list it read before the change. Calling `refresh()` re-reads the
553
+ catalog, including whether its endpoint now requires search criteria.
554
+
555
+ ##### Fixed
556
+
557
+ - **`lib-catalog-select` no longer reports "no results" when its endpoint arrives after the field
558
+ does.** A screen that reads the endpoint off a model it loads over HTTP binds an empty string
559
+ first. The field initialised against that, showed _no results found_ on the first open, and only
560
+ filled in once the user typed something and cleared it again.
561
+
562
+ With no endpoint the component takes its static-list path, finds an empty list and latches the
563
+ message. `searchEndpoint` was a plain input, so nothing re-ran the search when the real value
564
+ landed -- unlike `entriesList` and `filters`, which have always refreshed on change. It is now a
565
+ setter and re-reads the catalog, treating a new endpoint as a new catalog rather than carrying
566
+ over what the previous one reported about needing search criteria.
567
+
568
+ #### [1.6.6](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.6) - 2026-09-22
569
+
570
+ ##### Fixed
571
+
572
+ - **Group headings in a narrow navigation panel no longer spill their label past the edge.** On a
573
+ window under 768px the panel opens at its collapsed width, but each group still printed its full
574
+ label -- clipped mid-word against a bar with no room for it -- where it should show the short
575
+ divider the collapsed panel uses.
576
+
577
+ The width that widens the panel is declared inside the `md` query, so below that breakpoint the
578
+ panel stays narrow whatever its expanded class says. The heading, though, was keyed off that
579
+ class rather than the width it actually got. It now follows the width, with the overlay still
580
+ excluded -- that one does widen the panel at any size.
581
+
582
+ #### [1.6.5](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.5) - 2026-09-22
583
+
584
+ ##### Fixed
585
+
586
+ - **`lib-catalog-select` now offers its clear button for a value it did not see arrive.** A field
587
+ restored from a saved filter showed the value and its label but no way to empty it, so the only
588
+ escape was to clear the whole filter and start again.
589
+
590
+ The button asked an internal field that is written when the component itself takes the value --
591
+ a pick in the dropdown, or the initial read in `ngOnInit`. A form patched after the component
592
+ has initialised, and patched without an event, reaches the form control and the display and
593
+ nothing else. The button now asks the form control, which is the value that actually counts.
594
+
595
+ #### [1.6.4](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.4) - 2026-09-21
596
+
597
+ ##### Fixed
598
+
599
+ - **Host styles now apply.** Ten components declared their host block as `:host-context {` with no
600
+ argument. That is not a valid host selector, so every rule inside was silently dropped -- the
601
+ ribbon never became a flex row (its buttons stacked vertically), `lib-group-container` never got
602
+ its card background or border, and `lib-data-grid`, `lib-form-group`, `lib-group-accordion`,
603
+ `lib-group-scroll-spy`, `lib-catalog-select`, `lib-ribbon-group`, `lib-ribbon-button` and the
604
+ multi-select result grid all rendered unstyled at the host level. They are `:host {` now.
605
+
606
+ - `CatalogSelectComponent` subscribed to its `forkJoin` with an empty handler; it now subscribes
607
+ without one. No behavioural change.
608
+
609
+ #### [1.6.3](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.3) - 2026-09-20
610
+
611
+ ##### Fixed
612
+
613
+ - Declared `ngx-resize-observer` and `rxjs` in `peerDependencies`. `DataGridRowComponent` imports
614
+ `ngx-resize-observer` at runtime, but the package was never declared, so installing
615
+ `@zambon-dev/library` on its own left the dependency unresolved.
616
+ - Added a README to the published package.
617
+
618
+ ##### ⚠ Breaking Changes / Migration
619
+
620
+ **Requires Angular 22.** Every `@angular/*` peer range moved from `^19.1.0` to `^22.0.0`, and
621
+ the package is built and tested against Angular 22.1 with TypeScript 6.0.
622
+
623
+ Upgrade your application to Angular 22 first — Angular does not support skipping majors, so go
624
+ 19 → 20 → 21 → 22 with `ng update`. Angular 22 also requires Node.js 22.22.3 or newer.
625
+
626
+ - Requires `ngx-resize-observer` 4.x (the Angular 22 line); the peer range moved from `^3.0.0`.
627
+
628
+ **`FormInputComponent` outputs renamed.** Four outputs were named after native DOM events. Because
629
+ `<lib-form-input>` is a real element, `(change)` and `(input)` fired a consumer's handler twice --
630
+ once from the native event bubbling out of the inner control, once from the component -- and
631
+ `(blur)`/`(focus)` shadowed the native events. All four are typed `any`, so TypeScript could not
632
+ catch the mismatch.
633
+
634
+ Rename the bindings in your templates:
635
+
636
+ | Before | After |
637
+ | ---------- | ---------------- |
638
+ | `(blur)` | `(blurred)` |
639
+ | `(change)` | `(changed)` |
640
+ | `(focus)` | `(focused)` |
641
+ | `(input)` | `(inputChanged)` |
642
+
643
+ `(fixedValueChanged)` is unchanged, but its payload type is now `string` instead of `any` --
644
+ matching what it always emitted.
645
+
646
+ If you were relying on the double-fire, expect one call per event now instead of two.
647
+
648
+ #### [1.6.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.2) - 2026-09-20
649
+
650
+ ##### Fixed
651
+
652
+ - **A `lib-data-grid` column with no room from the start no longer drags its heading out of line.**
653
+ 1.6.1 removes the heading of a collapsed column, but only once that column had measured a real
654
+ width at some point. A column that never has any room does not, so it kept a heading over nothing
655
+ and pushed every column to its right along -- up to 263px on a fourteen-column screen, which is
656
+ the same drift 1.6.1 set out to remove.
657
+
658
+ Whether a zero is believable is now settled by the row rather than by what the column measured
659
+ before: if the row has a width the layout is live, so a column reporting zero really has
660
+ collapsed.
661
+
662
+ - **`lib-data-grid` headings no longer stay behind after you revisit a tab.** Re-activating a
663
+ detached tab puts the grid back in the page with its horizontal scroll at the start again, and no
664
+ scroll event follows to say so, so the headings kept the offset they had when you left: on a grid
665
+ scrolled 300px every heading sat 300px away from its column. The header now reconciles with the
666
+ body whenever the grid is re-measured, which is what re-inserting it triggers.
667
+
668
+ #### [1.6.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.1) - 2026-09-19
669
+
670
+ ##### Fixed
671
+
672
+ - **`lib-data-grid` headings now sit over their own column.** On a grid with many columns each
673
+ heading drifted further from its cells than the last -- up to 263px on a fourteen-column screen --
674
+ and scrolling sideways separated them completely.
675
+
676
+ The body lays the columns out as a CSS grid inside the CDK viewport; the heading row is a flex
677
+ row outside it that copies each column's measured width. Four things pulled the two apart:
678
+
679
+ - The heading cells could **shrink**, so once the columns together were wider than the view every
680
+ heading was compressed a little and the error accumulated across the row.
681
+ - The heading row **did not follow the body sideways**. The rows scroll inside the viewport and
682
+ the headings simply stayed where they were.
683
+ - A column was **measured once and never again**, while the body keeps resizing its
684
+ `minmax(x, min-content)` columns as virtual scrolling brings different content into view.
685
+ - A column measured at **zero** -- a `1fr` column collapsed for want of room -- left its heading
686
+ with no width at all, so it kept its text width and shoved everything to its right along.
687
+
688
+ Headings are now fixed to the measured width, translate with the body's horizontal scroll, track
689
+ every resize, and a heading whose column has collapsed is removed rather than left taking a gap
690
+ the body does not have. Measured in a running application the drift is zero across every column,
691
+ at rest and scrolled.
692
+
693
+ A column that declares no `size` is laid out as `minmax(0, 1fr)`, which is free to collapse. If
694
+ one of yours does, its heading now goes with it rather than staying behind as a label over
695
+ nothing. Give such a column a real minimum -- `size: 'minmax(8rem, 1fr)'` -- to keep both on
696
+ screen.
697
+
698
+ #### [1.6.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.6.0) - 2026-09-16
699
+
700
+ ##### Added
701
+
702
+ - **`DisplayControls`**, which records the form controls that exist only to show a catalog
703
+ selection's label. `lib-catalog-select` works in pairs — `controlName` holds the identifier,
704
+ `displayControlName` holds the text — and it creates the second itself when a screen does not
705
+ declare one, so a form carries controls nobody wrote down. It now marks whichever control it
706
+ drives as its display, and `framework-button-filters` reads that mark to keep those out of what
707
+ it submits.
708
+
709
+ The mark is held in a `WeakSet` keyed by the control instance, not by its name: two forms may
710
+ each have an `employeeName`, and only the one a catalog select drives is a display control.
711
+ Nothing about an application changes — declaring the display control yourself still works, and
712
+ it is marked just the same.
713
+
714
+ #### [1.5.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.5.0) - 2026-09-09
715
+
716
+ ##### Added
717
+
718
+ - **`SidebarConfigs.shouldDeriveAreasFromRootMenus`** — derive region headers from the menu tree
719
+ instead of from `SidebarMenu.region`. Off by default, so nothing changes until you opt in.
720
+
721
+ With it on, a top-level menu that has children **and no URL** stops being a collapsible node and
722
+ becomes an area header, with its children rendered flat beneath it as top-level items — icons
723
+ included. A top-level menu that has its own URL stays an item, and a parent that carries a URL
724
+ stays collapsible, so only the menus that were already acting purely as groups change shape.
725
+
726
+ The point is where the grouping lives. `region` is a label repeated on every item that belongs to
727
+ a group, matched by exact string equality: a typo silently splits one area into two, and the
728
+ area has no row of its own to carry an order or a translation. Derived from the tree, the area
729
+ _is_ a menu row — it already has a translated label and an order — and nothing has to be
730
+ duplicated across its items.
731
+
732
+ The children of an area are fetched **eagerly**, at load, because they are rendered without a
733
+ click and the lazy load a collapsible parent relies on would never fire. That is one extra
734
+ request per area. `region` keeps working exactly as before for anyone who prefers it; the two
735
+ mechanisms are independent and the flag chooses between them.
736
+
737
+ - **`SidebarService.loadChildrenFor(parentMenu)`** — loads a parent’s children and returns them as
738
+ an observable, for callers that need them before the user clicks. `loadChildren` is unchanged: it
739
+ is still the fire-and-forget variant that raises `childrenLoading` and `childrenFailed`, and it
740
+ now delegates to this one.
741
+
742
+ ##### ⚠ Breaking Changes / Migration
743
+
744
+ None. `shouldDeriveAreasFromRootMenus` defaults to `false`, so a sidebar keeps grouping by
745
+ `region` and keeps rendering top-level parents as collapsible nodes until you set it.
746
+
747
+ #### [1.4.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.4.1) - 2026-09-08
748
+
749
+ - Maintenance release (no consumer-facing changes were documented).
750
+
751
+ #### [1.4.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.4.0) - 2026-09-08
752
+
753
+ ##### Added
754
+
755
+ - **Sidebar menu items can now open an external destination instead of an internal route.**
756
+ `SidebarMenu` gained an optional `openMode` (`SidebarMenuOpenMode`): `Internal` (the default),
757
+ `ExternalNewTab`, or `ExternalEmbedded`. Read it through the exported
758
+ `toSidebarMenuOpenMode(menu.openMode)` helper rather than comparing the field directly — the
759
+ value is deserialized straight from your menu endpoint, so the helper accepts the mode as a
760
+ camelCase string, as a case-insensitive variant of it, or as the enum ordinal an ASP.NET Core
761
+ enum property serializes to. Anything unrecognized — an omitted field from a backend that
762
+ predates this feature, `null`, or a mode a newer version adds — degrades to `Internal`, so a
763
+ menu you have not touched behaves exactly as it does today.
764
+
765
+ - **`SidebarService.menuExternalUrlSelected`** — a separate output carrying the items whose mode
766
+ is not `Internal`. They are deliberately **not** announced on `menuUrlSelected`, which keeps its
767
+ existing contract: the URL it carries is always an Angular route. Acting on external items needs
768
+ `@zambon-dev/shared` (which opens the browser tab or the embedded view), or your own subscription
769
+ to the new output. Until then an external item highlights and does nothing, rather than sending
770
+ `https://…` to the router.
771
+
772
+ - **`SidebarConfigs.externalLinkText`** — the tooltip on items that open in a new browser tab.
773
+ Defaults to `Opens in a new browser tab` and, like `errorText` and `loadingText`, is rendered
774
+ as-is rather than through the translate pipe, so pass an already-localized string.
775
+
776
+ ##### Changed
777
+
778
+ - **Selection now follows the open mode.** An item that opens in a new browser tab is treated as an
779
+ action rather than a destination: it takes no selection pill and, importantly, does not clear the
780
+ selection of the view the user is still looking at. Items that open embedded become real
781
+ application tabs and keep today’s selection behaviour exactly. A parent that also carries a URL
782
+ still expands, so it cannot become impossible to open.
783
+
784
+ - **Items that open in a new browser tab show a trailing outbound glyph.** It sits in the same
785
+ absolutely-positioned slot as the parent chevron, so a long label still ellipsises inside the
786
+ anchor and the collapsed rail is unchanged, and it fades in with the sidebar like the other
787
+ affordances. Embedded items get no glyph — they stay inside the application. Items with no
788
+ `openMode`, or with `openMode: Internal`, render exactly as before.
789
+
790
+ ##### ⚠ Breaking Changes / Migration
791
+
792
+ None. `openMode` is optional, so a backend that does not send it produces today’s behaviour, and
793
+ `menuUrlSelected` is unchanged for internal items. If you subscribe to `menuUrlSelected` yourself
794
+ and want external items too, add a subscription to `menuExternalUrlSelected`.
795
+
796
+ #### [1.3.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.3.2) - 2026-07-30
797
+
798
+ ##### Added
799
+
800
+ - **Storybook: `Data Grid/Data Grid ▸ Multi Select Tall Rows` story** — multi-selection over rows
801
+ taller than the default `rowHeight` (41.6px), the height a row needs once it shows a thumbnail.
802
+ Misalignment in the selection column is obvious at that height, so this is the story to check the
803
+ selection checkbox against. Development-only: stories are excluded from the package build.
804
+
805
+ ##### Changed
806
+
807
+ - **`lib-ribbon` now hides itself when it contains no visible buttons.** A screen with no ribbon
808
+ actions — a dashboard, or any view that projects an empty `#ribbon` template — previously still
809
+ rendered the bar's border, background and padding around nothing, wasting vertical space at the top
810
+ of the page. This mirrors `lib-ribbon-group`, which already hid itself when none of its children
811
+ were visible. Ribbons with at least one visible group are unaffected. See
812
+ **⚠ Breaking Changes / Migration** below.
813
+
814
+ ##### Fixed
815
+
816
+ - **`lib-data-grid`'s multi-selection checkbox is now vertically centred in the row.** The checkbox is
817
+ styled with `vertical-align: super`, which lifts it off the middle of its line box — roughly 6px at
818
+ the default font size. At the default `configs.rowHeight` (41.6px) the drift is easy to miss, but on
819
+ taller rows (say `rowHeight: 56` for a row showing a thumbnail) the checkbox visibly hugged the top
820
+ of the row while the text columns stayed centred. The selection cell now lays its checkbox out as a
821
+ flex item, so it is centred by the box model and `vertical-align` no longer applies to it. The cell
822
+ is also tagged with a `selection` class, mirroring the header's `header-cell selection`. Horizontal
823
+ placement is unchanged, and no consumer changes are required.
824
+ - **Form inputs no longer render a stray bordered box when `type` is set as a static attribute.**
825
+ Angular keeps static attributes in the DOM even when a directive consumes them as an `@Input()`, so
826
+ `<lib-form-input-group type="checkbox">` left a `type="checkbox"` attribute on the host element —
827
+ where `@tailwindcss/forms`' tag-agnostic `[type="checkbox"]` base rules styled the host itself as a
828
+ checkbox (1px border, white fill, `height: 1rem`), drawing an empty-input box around the field row
829
+ and tinting its icon. `FormInputGroupComponent` and `FormInputComponent` now clear the attribute via
830
+ a host binding. This affects every value of `type` (`checkbox`, `date`, `number`, `password`, …);
831
+ both `type="checkbox"` and `[type]="'checkbox'"` now render identically, so existing call sites are
832
+ fixed with no changes required.
833
+ - **`lib-multi-editor` no longer discards rows the user did not type into.** `MultiEditorDataset.newData()`
834
+ exists so implementations can pre-fill a new row, but `onNewClick` reached the form through
835
+ `reset()` + `patchValue()`, which leaves it pristine — and the change tracker only records a row
836
+ while `formGroup.dirty`. Pre-filled values were therefore shown in the grid and then dropped from
837
+ the save batch. The visible symptom was worse than lost defaults: adding several rows and editing
838
+ only the last one saved just that row, because `onSaveClick` validates the current selection only,
839
+ so the earlier rows silently vanished on refresh. `newData()`'s values are now registered when the
840
+ row is added.
841
+ - **`lib-catalog-select` no longer renders an empty caption after a form reset.** `FormService.resetForm()`
842
+ clears every control and then patches the model back, so the component receives the value it already
843
+ held. It skipped refreshing the caption in that case, on the assumption that an unchanged value means
844
+ the caption is still on screen — but the reset had emptied the display control a moment earlier. The
845
+ field therefore rendered blank whenever the stored value equalled the control's initial value, which
846
+ is why it only ever showed on the entry the form defaults to: a `nonNullable` control declared as
847
+ `new FormControl(0)` bound to an enum whose first member is `0`, for instance. Values differing from
848
+ the initial one refreshed normally and always looked right, which made the fault read like a
849
+ translation or data problem rather than a reset one. The caption is now rewritten whenever it has
850
+ drifted from the selected entry, so the value alone no longer decides. Selects backed by a
851
+ `searchEndpoint` are unaffected, since their caption comes from the server-side selection rather than
852
+ `entriesList`. No consumer changes are required.
853
+
854
+ ##### ⚠ Breaking Changes / Migration
855
+
856
+ `lib-multi-editor` now sends rows that `newData()` pre-filled but the user never edited. Nothing to do
857
+ if your `newData()` returns an empty object (`{}`) — the common "blank row" convention — since a
858
+ value-less row is still skipped and the posted batch is byte-for-byte what it was before. If your
859
+ `newData()` _does_ return field values, those rows now reach `saveData()` where they were previously
860
+ dropped, so a batch may contain entries it did not before; verify your backend rejects or defaults
861
+ them as you expect.
862
+
863
+ `lib-ribbon` now hides itself when it has neither a visible `lib-ribbon-group` nor any other
864
+ projected content. No action needed if your ribbons use groups (the normal case). The rule is a
865
+ global selector at specificity (0,1,2), so overriding it requires `!important`. Because the bar can
866
+ now appear and disappear rather than merely resize, anything positioned relative to the ribbon may
867
+ shift when a button's visibility resolves (e.g. as permissions arrive).
868
+
869
+ #### [1.3.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.3.1) - 2026-07-28
870
+
871
+ > There is no `1.3.0` on npm: the version number was taken by an early publish that was later
872
+ > unpublished, and npm never allows a retired version number to be reused. The sidebar rework below
873
+ > therefore shipped as `1.3.1`. Upgrade straight from `1.2.0` to `1.3.1`.
874
+
875
+ ##### Added
876
+
877
+ - Semantic `--sidebar-*` design tokens covering the sidebar's surfaces, text, selection, glass, and
878
+ radii (`--sidebar-bg`, `--sidebar-nav-bg`, `--sidebar-text`, `--sidebar-text-muted`,
879
+ `--sidebar-item-hover-bg`, `--sidebar-item-selected-bg`, `--sidebar-blur`, `--sidebar-shadow`,
880
+ `--sidebar-tree-line`, `--sidebar-radius`, `--sidebar-item-radius`, …) so consumers can re-theme
881
+ the sidebar entirely via CSS custom properties.
882
+ - **Menu regions**: an optional `SidebarMenu.region` label groups top-level items under an uppercase
883
+ group header (e.g. "MAIN"). Items without a `region` render ungrouped, so it stays
884
+ backward-compatible. When the rail is collapsed, each header cross-fades to a short separator line
885
+ so the groups remain visually distinct. The grouping type is exported as `SidebarRegion`.
886
+
887
+ ##### Changed
888
+
889
+ - Selected menu items now render as a rounded **pill** highlight (inset when expanded) instead of the
890
+ previous 4px left accent bar.
891
+ - The sidebar is now a **translucent, blurred "glass" panel** — rounded corners, a soft drop shadow,
892
+ and a semi-transparent brand surface — rather than a solid, square, edge-to-edge rail (all
893
+ token-driven via `--sidebar-bg`, `--sidebar-blur`, `--sidebar-shadow`, `--sidebar-radius`).
894
+ - **Child (sub-menu) items** now render a rounded **tree-connector** line instead of a menu icon;
895
+ top-level items keep their icons.
896
+ - Collapsing/expanding the rail is now **animated end to end**: the toggle chevron and the
897
+ expandable-parent caret rotate in the same direction, the selection pill inset and item icons ease
898
+ into place, and region headers cross-fade to their separators — all in sync with the rail width.
899
+
900
+ ##### ⚠ Breaking Changes / Migration
901
+
902
+ The sidebar surface is now **translucent** ("glass"). If your app does not place a colored backdrop
903
+ behind the sidebar, the rail will look faint over plain/neutral content — either add a
904
+ colored/gradient backdrop behind it (see `@shared`'s `--app-backdrop` in `MainLayoutComponent`) or
905
+ override `--sidebar-bg` (and the other `--sidebar-*` surface tokens) with opaque values to keep a
906
+ solid rail.
907
+
908
+ #### [1.2.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.2.0) - 2026-07-24
909
+
910
+ ##### Added
911
+
912
+ - `SidebarComponent` now renders its own collapse/expand toggle: a light circular chevron "handle"
913
+ that straddles the sidebar's right edge (`angle-left` to collapse when expanded, `angle-right` to
914
+ expand when collapsed), matching the conventional rail-collapse affordance.
915
+ - `SidebarComponent` now has a content-projection slot (`<ng-content>`) rendered as a footer at the
916
+ bottom of the sidebar — for extra elements after the menu (e.g. an app version). It is hidden when
917
+ the rail is collapsed and collapses entirely when nothing is projected.
918
+
919
+ ##### Changed
920
+
921
+ - `SidebarComponent` no longer renders the logo or the user-profile header — it now shows only the
922
+ navigation toggle and menu. Application branding and user identity are expected to live in the top
923
+ bar (`shared-top-bar` in `@shared`).
924
+ - Sidebar menu items now use an on-palette hover surface (a subtle light overlay) instead of the
925
+ previous off-palette `slate-700`.
926
+
927
+ ##### ⚠ Breaking Changes / Migration
928
+
929
+ If you relied on the sidebar's logo (`SidebarConfigs.logoCollapsedPath` / `logoExpandedPath`) or its
930
+ user profile (`SidebarService.getUserProfile()`), move that presentation to the top bar. Those
931
+ config options and the `getUserProfile()` method still exist but are no longer consumed by
932
+ `SidebarComponent`.
933
+
934
+ #### [1.1.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/library-v1.1.1) - 2026-06-17
935
+
936
+ - Baseline release: the changelog starts being tracked from this version. Earlier history is
937
+ available via [GitHub Releases](https://github.com/RicardoZambon/ZLibraries/releases) and the
938
+ `library-v*` tags.
939
+
940
+ ### Framework
941
+
942
+ Tabbed navigation, view scaffolding, ribbon buttons and modals. Published as `@zambon-dev/framework` through 5.0.5.
943
+
944
+ #### [5.0.5](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v5.0.5) - 2026-10-24
945
+
946
+ - Maintenance release (no consumer-facing changes were documented).
947
+
948
+ #### [5.0.4](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v5.0.4) - 2026-10-24
949
+
950
+ ##### Changed
951
+
952
+ - **Every component in this package now renders under any change-detection strategy the host
953
+ application chooses, zoneless included.** The ribbon buttons were the last group still
954
+ declaring `Eager`, and the one that depended on it most: a button answers to things that
955
+ arrive after the first paint -- an access check resolving, a grid announcing a selection, a
956
+ load finishing -- and none of that reached the screen beneath a clean `OnPush` ancestor.
957
+ Their state is signals now, and `src/lib/change-detection-contract.spec.ts` asserts over the
958
+ public API that nothing in this package opts out again.
959
+
960
+ - **A view's `loading` flag notifies.** `ViewBase` backs it with a signal behind the same
961
+ accessors, so a subclass reading or writing `this.loading` needs no change and its own
962
+ template finally reflects it.
963
+
964
+ - **The legacy form's `viewMode` notifies.** Same treatment, same accessors. It is what the
965
+ legacy Edit and Save buttons render from.
966
+
967
+ ##### Fixed
968
+
969
+ - **A button no longer asks twice whether its actions are allowed.** `allowedActions` fired the
970
+ check from its setter _and_ the constructor fired it again before any input had arrived, so
971
+ every button with an access list made two requests. It is one stream over the input now.
972
+
973
+ - **A button with an access list is disabled until the answer arrives**, rather than clickable
974
+ and then hidden. This is what `RibbonButtonComponent` already does with a dropdown option
975
+ whose access has not come back.
976
+
977
+ - **The confirm and delete buttons show their modal's spinner again.** Their footer button read
978
+ `modal.isLoading` through a template reference, which became a signal object in 5.0.3 -- and
979
+ an object is always truthy, so the spinner was permanently on.
980
+
981
+ ##### ⚠ Breaking Changes / Migration
982
+
983
+ - **`BaseButton`'s state is signals.** Binding to any of them from a template is unchanged.
984
+ Code that touches them is not:
985
+
986
+ | Before | After |
987
+ | ---------------------------- | -------------------------- |
988
+ | `this.disabled = true` | `this.disabled.set(true)` |
989
+ | `this.loading = true` | `this.loading.set(true)` |
990
+ | `this.options = [...]` | `this.options.set([...])` |
991
+ | `this.options.find(...)` | `this.options().find(...)` |
992
+ | `this.allowedActions` | `this.allowedActions()` |
993
+ | `this.iconSize` | `this.iconSize()` |
994
+ | `this.button.startLoading()` | `this.startLoading()` |
995
+
996
+ `startLoading()` and `finishLoading(status)` keep their signatures. `disabled`, `loading` and
997
+ `options` are `model()`, so a subclass can still drive them; the rest are `input()`.
998
+
999
+ - **Every button component's own inputs are signals too**, and so are the view queries they
1000
+ expose. The ones a host application is most likely to read directly:
1001
+ `ButtonFiltersComponent.modal` is now a `Signal<ModalComponent | undefined>`, and
1002
+ `ButtonViewsLegacyComponent.views` is an `input()`. A template reading one through a
1003
+ reference -- `#btn` then `btn.disabled` -- needs `btn.disabled()`; without the call it reads
1004
+ the signal object, which is always truthy.
1005
+
1006
+ - **`ViewBase` has a new protected member, `loadingState`**, the signal behind `loading`. A
1007
+ subclass that already declares something by that name has to rename it.
1008
+
1009
+ - **`DefaultDetailsTabViewComponent`'s protected state is signals**: `containerIcon`,
1010
+ `containerTitle` and `detailsViewRoute`. A subclass that reads one needs `()`; one that
1011
+ assigns one needs `.set(...)`.
1012
+
1013
+ - The New, Edit and Views buttons only appear once a record exists, which they read from
1014
+ `DataProviderService`. That now announces its entity id, so install the `@zambon-dev/library`
1015
+ released alongside this version rather than an older 4.x.
1016
+
1017
+ #### [5.0.3](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v5.0.3) - 2026-10-24
1018
+
1019
+ ##### Changed
1020
+
1021
+ - **The breadcrumbs, the confirm modal and the error modal render under any change-detection
1022
+ strategy.** The breadcrumbs were the clearest case of a getter over state a service mutates
1023
+ elsewhere, and both modals are driven imperatively by a parent calling `showModal` from a
1024
+ subscription -- a message or a title landed with nothing marking the view dirty.
1025
+
1026
+ - **The ribbon strip renders without the view forcing a change-detection pass.**
1027
+ `DefaultTabViewComponent` holds the published template in a signal, and `TabViewService` holds
1028
+ its active view in one. The `detectChanges()`/`markForCheck()` pair they used to need is gone,
1029
+ and with it the rule that the ribbon template could never be assigned after the first pass --
1030
+ a signal write there schedules another pass rather than throwing NG0100.
1031
+
1032
+ ##### ⚠ Breaking Changes / Migration
1033
+
1034
+ - **`ConfirmModalComponent` and `ErrorModalComponent` expose their inputs as signals.** Binding to
1035
+ them from a template is unchanged. Code that sets one before showing the modal keeps working
1036
+ through `.set(...)`, because the ones a parent drives -- `errorMessage`, `isLoading`, and the
1037
+ error modal's `title` and `subtitle` -- are `model()`. Code that _reads_ one needs `()`:
1038
+ `confirmModal.isLoading()` rather than `confirmModal.isLoading`.
1039
+
1040
+ - **`DefaultTabViewComponent` no longer takes constructor arguments.** It injects `TabViewService`
1041
+ itself and no longer injects `ChangeDetectorRef` at all. A subclass calling
1042
+ `super(changeDetectorRef, tabViewService)` should call `super()`, or drop its constructor.
1043
+
1044
+ - **`DefaultTabViewComponent.ribbonTemplate` is a `Signal<TemplateRef | undefined>`.** Read it as
1045
+ `ribbonTemplate()`. `TabViewService.onUpdateRibbonTemplate` is unchanged, so anything
1046
+ subscribing to it keeps working.
1047
+
1048
+ #### [5.0.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v5.0.2) - 2026-10-24
1049
+
1050
+ ##### Changed
1051
+
1052
+ - **Internal only: `ButtonFiltersComponent` reads `RibbonButtonComponent.loading` as a signal.**
1053
+ The compiler caught the old form: in a condition, the signal object itself is always true.
1054
+ Nothing in this package's own API changed.
1055
+
1056
+ #### [5.0.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v5.0.1) - 2026-10-24
1057
+
1058
+ ##### Changed
1059
+
1060
+ - **Internal only: call sites moved with `@zambon-dev/library`'s modal API.** `ModalComponent.isShown`
1061
+ became `isOpen()`, and the confirm and error modals delegate to it through a signal view query.
1062
+ Nothing in this package's own API changed.
1063
+
1064
+ ##### Fixed
1065
+
1066
+ - **The peer range on its sibling packages now admits the majors they were released at.** The last
1067
+ release published `@zambon-dev/library` 4.0.0, `@zambon-dev/framework` 5.0.0 and
1068
+ `@zambon-dev/shared` 6.0.0, but the ranges here still asked for the previous majors, so an
1069
+ application installing two of them together got `ERESOLVE`. Nothing else changed.
1070
+
1071
+ #### [5.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v5.0.0) - 2026-10-24
1072
+
1073
+ ##### Changed
1074
+
1075
+ - **The view and button base classes no longer declare a change-detection strategy.** This is not
1076
+ a behaviour change: Angular has never inherited `changeDetection`, so the `Eager` these carried
1077
+ applied to the base class itself -- which is never instantiated -- and never to your subclasses.
1078
+ Your components have compiled to OnPush since you upgraded to Angular 22, whatever the base said.
1079
+ The declaration is gone because it described nothing.
1080
+
1081
+ ##### Fixed
1082
+
1083
+ - **Ribbon buttons appear once their access check resolves.** `BaseButton` resolved visibility
1084
+ from an asynchronous `checkActionsAreAllowed` call and assigned a plain field, which told Angular
1085
+ nothing. Under Angular 22's default strategy the button stayed hidden until something else forced
1086
+ a render.
1087
+ - **The tab strip renders the tabs you open.** In an application whose root component uses
1088
+ Angular 22's default strategy, opening anything from the menu changed the URL and left the tab
1089
+ bar, the view, the ribbon and the breadcrumbs blank: `TabsComponent` reads the open tabs
1090
+ through getters on `TabService`, which mutates its arrays in place, and nothing told the
1091
+ component to render. `TabService` now announces its own changes, so the tabs, the titles that
1092
+ resolve later, and the breadcrumbs all reach the screen. Found by running SecurityHub against
1093
+ this package rather than by a test -- which is why one now guards it.
1094
+
1095
+ - **The tab strip re-measures when it narrows.** Bringing the selected tab back into view hung off
1096
+ `ngAfterViewChecked`, which only ever ran because zone.js produced a pass on every event. The
1097
+ `ResizeObserver` -- which is how a browser actually reports a box changing -- now does the full
1098
+ re-measure, and the post-render work uses `afterEveryRender`.
1099
+
1100
+ ##### ⚠ Breaking Changes / Migration
1101
+
1102
+ - **`BaseButton.visible` is now `isVisible`, and it is a signal.** Every component that extends
1103
+ `BaseButton` is affected, in its template and its code:
1104
+
1105
+ | Replace | With |
1106
+ | --------------------- | ----------------------- |
1107
+ | `@if (visible)` | `@if (isVisible())` |
1108
+ | `this.visible` | `this.isVisible()` |
1109
+ | `!isAccessLoaded` | `!isAccessLoaded()` |
1110
+ | `this.isAccessLoaded` | `this.isAccessLoaded()` |
1111
+
1112
+ Both were renamed or re-typed deliberately, so a call site you miss is a compile error rather
1113
+ than a button that quietly never appears. Before you start, turn on `strictTemplates` and
1114
+ `"interpolatedSignalNotInvoked": "error"` under `angularCompilerOptions.extendedDiagnostics` in
1115
+ your `tsconfig.json`, which makes `{{ someSignal }}` a build failure too.
1116
+
1117
+ - **A component that delegates its visibility to an inner button needs a signal view query.** If
1118
+ you implement `RibbonGroupChild` by wrapping a `framework-button` and forwarding its state,
1119
+ a rename alone is not enough -- a `computed` reading a plain `@ViewChild` never re-runs when
1120
+ the query resolves:
1121
+
1122
+ ```ts
1123
+ // before
1124
+ @ViewChild(ButtonComponent) private buttonComponent!: ButtonComponent;
1125
+ public get visible(): boolean {
1126
+ return this.buttonComponent?.visible ?? false;
1127
+ }
1128
+
1129
+ // after
1130
+ private readonly buttonComponent = viewChild(ButtonComponent);
1131
+ public readonly isVisible: Signal<boolean> = computed(() => this.buttonComponent()?.isVisible() ?? false);
1132
+ ```
1133
+
1134
+ Every other read of that query moves to `this.buttonComponent()?.…` as well.
1135
+
1136
+ - **`TabsComponent` no longer injects `ChangeDetectorRef`.** Only relevant if you construct it
1137
+ yourself; used through its selector, nothing changes.
1138
+
1139
+ #### [4.0.5](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v4.0.5) - 2026-10-22
1140
+
1141
+ ##### Fixed
1142
+
1143
+ - **Going back in one tab no longer drops a view another tab still has open.** Every open tab keeps
1144
+ its views in the reuse cache — the one it shows or one in its history — and a view should leave
1145
+ that cache only once no tab has its URL open. `navigateCurrentTabBack` cleared the handle of every
1146
+ entry it removed regardless, so a record open in two tabs lost its cached view in the other one
1147
+ too. It now checks first, as `closeTab` and `replaceCurrentTabSubView` already did.
1148
+
1149
+ #### [4.0.4](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v4.0.4) - 2026-10-10
1150
+
1151
+ ##### Fixed
1152
+
1153
+ - **A record’s non-form views can render their edit button again.** `framework-button-edit` asks
1154
+ for `FormService` and `DataProviderService` together, and the details shell provided only the
1155
+ second — so a child list routed as its own view (Aplicações → Visões → Business Places, Centros
1156
+ de Custo) threw `NG0201: No provider found for FormService` before it rendered at all. The shell
1157
+ provides one now; a form view’s own provider still shadows it, so forms are unchanged.
1158
+
1159
+ - **The breadcrumbs no longer sit further from the ribbon than the ribbon does from its content.**
1160
+ They carried `padding-bottom` _and_ `margin-bottom` around their rule, 8px + rule + 8px, against
1161
+ the 8px below the ribbon. The tab panel spaces its children with a gap now, as the views do, and
1162
+ the breadcrumbs carry neither — 9px, the gap plus the rule itself.
1163
+
1164
+ #### [4.0.3](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v4.0.3) - 2026-10-02
1165
+
1166
+ ##### Fixed
1167
+
1168
+ - **Closing a tab no longer sends the tab beside it back to its root.** The focused tab was
1169
+ navigated to `activeTabs[i]`, which is each tab’s _root_ — the entry the strip takes its label
1170
+ from — rather than the top of its history. A tab opened as a list and then drilled into a record
1171
+ went back to the list, under the record’s own tab, until the tab was clicked again. Every other
1172
+ navigation in the service already took the top of the stack; this one is now shared with
1173
+ `activateTab` so the two cannot drift apart.
1174
+
1175
+ - **The gap between the ribbon and a list is the one the view asks for.** `router-outlet` renders
1176
+ nothing but is still a flex item, and a flex item of zero height takes a gap on each side, so a
1177
+ list view showed 1rem where its host asked for 0.5rem — while a detail view, whose outlet is
1178
+ nested inside `lib-group-container`, showed 0.5rem. The outlet is out of the flow now, which
1179
+ leaves the spacing to the host rather than to what each view happens to contain.
1180
+
1181
+ #### [4.0.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v4.0.2) - 2026-10-01
1182
+
1183
+ ##### Fixed
1184
+
1185
+ - **Loading the package under Jest no longer throws
1186
+ `Cannot access 'ButtonNewLegacyComponent' before initialization`.** `LegacyTabViewDetails`
1187
+ queries its New button with `@ViewChild`, and the legacy buttons import the views barrel back,
1188
+ so the class is defined before the button: in the published bundle the button sits further down
1189
+ the file. An application build never noticed, because its linker moves the reference into a
1190
+ function, but a JIT runtime reads it the moment the package is imported, so every spec that
1191
+ imported anything from `@zambon-dev/framework` failed before running a test. The query is a
1192
+ forward reference now. Nothing to change in applications, and specs excluded for this reason can
1193
+ be put back.
1194
+
1195
+ #### [4.0.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v4.0.1) - 2026-09-26
1196
+
1197
+ ##### Fixed
1198
+
1199
+ - **The selected tab and the panel no longer have a seam between them.** The panel drew its rim
1200
+ on all four sides, and along the top that rim met the selected tab’s fill rather than the
1201
+ backdrop it was meant for: `--tabs-border` is 60% white against the tab’s 88%, so a _darker_
1202
+ hairline ran exactly along the join and made the tab read as a separate, slightly lighter shape
1203
+ sitting on the panel. The top edge belongs to the tabs, which carry no border, so the panel no
1204
+ longer draws one there.
1205
+
1206
+ #### [4.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v4.0.0) - 2026-09-26
1207
+
1208
+ ##### Fixed
1209
+
1210
+ - **The filters button no longer waits for an event that has already happened.** It starts
1211
+ disabled and learns otherwise from the grid's `loadFinished`, which is right only for a
1212
+ button that exists before its grid first loads. A ribbon template is re-created whenever a
1213
+ record's views are switched, so a button built then reached a grid that had finished loading
1214
+ long ago and stayed unclickable until something made the grid load again. It now reads the
1215
+ state from the dataset as well.
1216
+
1217
+ #### [3.2.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v3.2.2) - 2026-09-26
1218
+
1219
+ ##### Fixed
1220
+
1221
+ - **Component styles are compiled again.** The Tailwind 4 migration left every component's own
1222
+ stylesheet unprocessed in the published bundle, so its `@apply` rules reached the browser as an
1223
+ at-rule the browser does not know and it dropped every rule that used one. In practice the
1224
+ components rendered unstyled in any application installing 3.2.1. ng-packagr resolves its
1225
+ PostCSS configuration from the library directory rather than from the workspace root, and the
1226
+ migration added one only at the root. Nothing failed along the way: Storybook compiles from
1227
+ source and never sees the packaged artifact, which is why `tools/check-packaged-styles.mjs` now
1228
+ runs against that artifact in CI and again before every publish.
1229
+
1230
+ #### [3.2.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v3.2.1) - 2026-09-26
1231
+
1232
+ ##### Fixed
1233
+
1234
+ - **Both tab scroll controls stay put while the strip overflows**, disabled at the end they point
1235
+ at rather than removed. Rendering them per-direction meant the one being reached for disappeared
1236
+ on arrival: the strip changed width under the pointer and the click landed on nothing, which is
1237
+ what made the arrows look like they sometimes did nothing.
1238
+
1239
+ - **The last tab no longer floats past the panel's rounded corner.** While a tab or a scroll
1240
+ control holds the strip's right edge, the panel's top-right corner is square, because the card's
1241
+ corner is up on the strip; rounding both left whatever sat at the edge standing over the sweep
1242
+ where the panel had curved away. With the tabs short of the edge the corner is the panel's again
1243
+ and stays round.
1244
+
1245
+ #### [3.2.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v3.2.0) - 2026-09-25
1246
+
1247
+ ##### Changed
1248
+
1249
+ - **Tailwind CSS 4.** The component styles in this package are compiled against Tailwind 4 now.
1250
+ They are expanded at build time, so nothing in the published bundle changed shape -- but the
1251
+ application consuming it has to be on Tailwind 4 as well.
1252
+
1253
+ #### [3.1.5](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v3.1.5) - 2026-09-25
1254
+
1255
+ ##### Fixed
1256
+
1257
+ - **The selected tab and the panel are the same colour again.** Both already named an 88% white,
1258
+ but the panel lays its fill over a backdrop it has blurred and the tab laid the identical fill
1259
+ over an unblurred one — two results from one token, and a visible seam between them. The tab
1260
+ carries the same blur now.
1261
+
1262
+ - **A tab reaching the right edge no longer sticks out past the panel's rounded corner.** The
1263
+ strip spans the panel's full width, so once the tabs filled it the last one's square corner was
1264
+ left standing over the point where the panel had already curved away. The strip carries the
1265
+ panel's corner now, which clips whatever reaches it; the scroll chevron does the same when it
1266
+ is the element at the edge.
1267
+
1268
+ - **The tab panel publishes `--surface-radius`** for whatever sits directly inside it — the
1269
+ container's radius minus its own padding and border, which is the value that keeps an inner box
1270
+ looking parallel to it. `@zambon-dev/library`'s grid and group container read it.
1271
+
1272
+ ##### ⚠ Breaking Changes / Migration
1273
+
1274
+ **Requires Tailwind CSS 4**, and `@zambon-dev/shared` 5.x, which ships the theme. The migration
1275
+ steps are in the `@zambon-dev/shared` changelog for this release; there is nothing specific to
1276
+ this package to do beyond following them.
1277
+
1278
+ #### [3.1.4](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v3.1.4) - 2026-09-25
1279
+
1280
+ ##### Fixed
1281
+
1282
+ - **The peer range on `@zambon-dev/library` now matches the version this package is built against.**
1283
+ It still asked for `^2.0.0` after the library moved to 3.x, so installing `framework` and
1284
+ `library` together at their current releases failed `npm install` with ERESOLVE. An application
1285
+ could only get past it with `--legacy-peer-deps` or an override, both of which switch off the
1286
+ peer checking that would have caught a genuine mismatch.
1287
+
1288
+ #### [3.1.3](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v3.1.3) - 2026-09-24
1289
+
1290
+ ##### Fixed
1291
+
1292
+ - **A confirm or error modal no longer adds a gap below the page.** Both render nothing but a
1293
+ fixed-position modal, yet their host still counted as a child in the parent's layout, so a flex
1294
+ column with a gap reserved a slot for it under the last visible element -- which is why a detail
1295
+ view and some list views sat further from the bottom of the window than the rest.
1296
+
1297
+ #### [3.1.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v3.1.2) - 2026-09-22
1298
+
1299
+ ##### Fixed
1300
+
1301
+ - **A filtered view no longer loses its Filter and Clear filters state when another details tab is
1302
+ opened.** The grid stayed filtered while the ribbon came back as though nothing were filtered, so
1303
+ the only way out was to filter again. Opening a list view never did it -- which is what made the
1304
+ behaviour look arbitrary.
1305
+
1306
+ Every details tab keeps its router subscription alive, including the ones the reuse strategy has
1307
+ detached, and the lookup that finds the active details route reads the router, which names
1308
+ whichever tab the navigation went to. A detached tab therefore adopted the incoming tab's base
1309
+ path and switched itself to that tab's view, tearing its own ribbon down and building a fresh
1310
+ one -- and the new buttons knew nothing of the filter still held by the grid. A list view is not
1311
+ a details route, so the lookup found nothing and the tab was left alone.
1312
+
1313
+ A tab now answers only to navigations matching its own route definition, which still covers the
1314
+ one navigation that legitimately changes its path: the redirect from `/new` to `/:id` after a
1315
+ save.
1316
+
1317
+ #### [3.1.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v3.1.1) - 2026-09-22
1318
+
1319
+ ##### Fixed
1320
+
1321
+ - **Switching views in a details tab no longer moves the buttons to the wrong view.** Opening a
1322
+ second view showed the first view's buttons instead of its own, and going back to the first view
1323
+ left it with no buttons at all -- on the employee screen, Histórico showed the Detalhes ribbon and
1324
+ Detalhes then showed an empty one.
1325
+
1326
+ 1.4.1 taught a tab to claim a ribbon published before its view had been named, which is what a tab
1327
+ opened straight at a non-default view needs. It claimed on every naming, though, and the empty id
1328
+ is not a spare bucket: it is the default view's own id, since that view is the one whose URL
1329
+ carries no sub-path. From the second switch onwards the template being claimed belonged to the
1330
+ view being left. Only the first naming may claim it now.
1331
+
1332
+ Also released as 1.4.2 on the 1.4.x line, for applications still on Angular 19.
1333
+
1334
+ #### [3.1.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v3.1.0) - 2026-09-21
1335
+
1336
+ ##### Added
1337
+
1338
+ - **Tabs close on middle click**, and with `Delete` while a tab has focus.
1339
+ - **The tab strip scrolls.** It was `overflow: hidden`, so once the tabs were wider than the bar the
1340
+ ones past the right edge could not be reached at all. Selecting a tab also scrolls it into view.
1341
+ - **Scroll controls at each end of the strip**, shown only while there is something to scroll to.
1342
+ - **A vertical mouse wheel over the strip scrolls it horizontally**, so a plain wheel mouse can
1343
+ reach an off-screen tab without going to the controls. Trackpad and `Shift`+wheel gestures, which
1344
+ already produce horizontal deltas, are left to the browser.
1345
+
1346
+ ##### Changed
1347
+
1348
+ - **Tabs restyled to match the sidebar's glass**: a translucent, blurred, rounded panel, with the
1349
+ active tab and the content sharing one fill so they read as a single card. Every colour, radius
1350
+ and dimension comes from a `--tabs-*` custom property now, so the look can be retuned without
1351
+ touching the component. Breadcrumbs follow the same tokens.
1352
+
1353
+ The glass is painted on a `::before` layer rather than on the panel itself. `backdrop-filter`
1354
+ makes an element the containing block for its `position: fixed` descendants, and views render
1355
+ hidden, viewport-width modal overlays inside the panel — filtering it directly pulled those into
1356
+ its scroll width and produced a phantom horizontal scrollbar.
1357
+
1358
+ - **The tablist is a single tab stop.** `Tab` reaches the selected tab and the arrow keys move
1359
+ between tabs from there, with `Home`/`End` for the ends. Previously every tab was its own tab
1360
+ stop and there was no arrow-key support.
1361
+ - **The panel is a real `tabpanel`**, with `aria-controls` on each tab and `aria-labelledby` on the
1362
+ panel, so assistive technology can tell which panel a tab owns.
1363
+ - **Close buttons are named after their tab** — "Close Acme Industries" rather than six identical
1364
+ "Close tab". `Tabs-CloseTab` takes a `title` parameter now; if you have overridden that key in
1365
+ your own translations, add `{{title}}` to it.
1366
+ - **A tab keeps its width while its title loads.** The spinner sits beside the label instead of
1367
+ replacing it, so the strip no longer reflows as each title resolves.
1368
+ - **Tabs compress before the strip scrolls**, down to `--tabs-item-min-width`, so a few more fit
1369
+ before any of them goes off screen. Overflowing tabs fade out at the edges instead of being cut
1370
+ off mid-tab.
1371
+ - **The selected tab is pulled back into view when the strip narrows**, not only when the selection
1372
+ changes. Resizing the window or collapsing the sidebar used to be able to leave you looking at a
1373
+ panel whose tab was nowhere on screen.
1374
+ - Breadcrumb separators are drawn in CSS rather than as a FontAwesome glyph codepoint, which was
1375
+ tied to a specific FontAwesome major.
1376
+
1377
+ #### [3.0.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v3.0.1) - 2026-09-21
1378
+
1379
+ - Maintenance release (no consumer-facing changes were documented).
1380
+
1381
+ #### [3.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v3.0.0) - 2026-09-21
1382
+
1383
+ - Maintenance release (no consumer-facing changes were documented).
1384
+
1385
+ #### [2.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v2.0.0) - 2026-09-21
1386
+
1387
+ ##### Changed
1388
+
1389
+ - **Accessibility.** `TabsComponent` renders proper `tablist`/`tab` semantics with `aria-selected`,
1390
+ its tabs are keyboard-activatable and the close control is a labelled `<button>`.
1391
+ `TabBreadcrumbsComponent` entries are focusable and activate with `Enter`/`Space`.
1392
+ - Added the `Tabs-CloseTab` translation key (en, pt) for the tab close button's accessible name.
1393
+
1394
+ > **The 1.4.x line is retired.** It carried the framework for applications still on Angular 19
1395
+ > while `main` moved to Angular 22, publishing under its own `1.4.x` dist-tag. Its branch is gone;
1396
+ > the final state is kept in the `angular-19/framework-1.4.x` tag. Every fix recorded below also
1397
+ > shipped in a 3.x or 4.x release, which requires Angular 22.
1398
+
1399
+ #### [1.4.7](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.4.7) - 2026-10-01
1400
+
1401
+ ##### Fixed
1402
+
1403
+ - **Loading the package under Jest no longer throws `Cannot access 'ButtonNewLegacyComponent'
1404
+ before initialization`.** `LegacyTabViewDetails` queries its New button with `@ViewChild`, and
1405
+ the legacy buttons import the views barrel back, so the class is defined before the button: in
1406
+ the published bundle the button sits further down the file. An application build never noticed,
1407
+ because its linker moves the reference into a function, but a JIT runtime reads it the moment
1408
+ the package is imported, so every spec that imported anything from `@zambon-dev/framework`
1409
+ failed before running a test. The query is a forward reference now. Nothing to change in
1410
+ applications, and specs excluded for this reason can be put back.
1411
+
1412
+ #### [1.4.6](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.4.6) - 2026-09-24
1413
+
1414
+ ##### Fixed
1415
+
1416
+ - **A confirm or error modal no longer adds a gap below the page.** Both render nothing but a
1417
+ fixed-position modal, yet their host still counted as a child in the parent's layout, so a flex
1418
+ column with a gap reserved a slot for it under the last visible element -- which is why a detail
1419
+ view and some list views sat further from the bottom of the window than the rest.
1420
+
1421
+ #### [1.4.5](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.4.5) - 2026-09-22
1422
+
1423
+ ##### Fixed
1424
+
1425
+ - **A filtered view no longer loses its Filter and Clear filters state when another details tab is
1426
+ opened.** The grid stayed filtered while the ribbon came back as though nothing were filtered, so
1427
+ the only way out was to filter again. Opening a list view never did it -- which is what made the
1428
+ behaviour look arbitrary.
1429
+
1430
+ Every details tab keeps its router subscription alive, including the ones the reuse strategy has
1431
+ detached, and the lookup that finds the active details route reads the router, which names
1432
+ whichever tab the navigation went to. A detached tab therefore adopted the incoming tab's base
1433
+ path and switched itself to that tab's view, tearing its own ribbon down and building a fresh
1434
+ one -- and the new buttons knew nothing of the filter still held by the grid. A list view is not
1435
+ a details route, so the lookup found nothing and the tab was left alone.
1436
+
1437
+ A tab now answers only to navigations matching its own route definition, which still covers the
1438
+ one navigation that legitimately changes its path: the redirect from `/new` to `/:id` after a
1439
+ save.
1440
+
1441
+ #### [1.4.4](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.4.4) - 2026-09-22
1442
+
1443
+ ##### Fixed
1444
+
1445
+ - **Switching views in a details tab no longer moves the buttons to the wrong view.** Opening a
1446
+ second view showed the first view's buttons instead of its own, and going back to the first view
1447
+ left it with no buttons at all -- on the employee screen, Histórico showed the Detalhes ribbon and
1448
+ Detalhes then showed an empty one.
1449
+
1450
+ 1.4.1 taught a tab to claim a ribbon published before its view had been named, which is what a tab
1451
+ opened straight at a non-default view needs. It claimed on every naming, though, and the empty id
1452
+ is not a spare bucket: it is the default view's own id, since that view is the one whose URL
1453
+ carries no sub-path. From the second switch onwards the template being claimed belonged to the
1454
+ view being left. Only the first naming may claim it now.
1455
+
1456
+ #### [1.4.3](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.4.3) - 2026-09-21
1457
+
1458
+ ##### Fixed
1459
+
1460
+ - **Host styles now apply.** `DefaultTabViewComponent`, `DefaultDetailsTabViewComponent`,
1461
+ `TabBreadcrumbsComponent` and `ButtonFiltersComponent` declared their host block as
1462
+ `:host-context {` with no argument, so every rule inside was silently dropped. Visible effects:
1463
+ no gap between the ribbon and the view below it, and breadcrumbs with neither their bottom rule
1464
+ nor their spacing. They are `:host {` now.
1465
+
1466
+ - `ButtonFiltersComponent.validateFormFunction` was typed `Function`, which accepted any
1467
+ function-like value. It is now `() => void`, matching how it is actually invoked. Consumers
1468
+ passing a function that takes arguments or returns a value will now see a type error.
1469
+ - Removed empty constructors from `ConfirmModalComponent` and `TabViewService`.
1470
+
1471
+ - `DefaultDetailsTabViewComponent` imported `ButtonViewsComponent` through the `buttons` barrel,
1472
+ which re-exports `./legacy`, whose buttons import the `views` barrel, which re-exports this
1473
+ component. The cycle left one of the component's own imports undefined at evaluation time and
1474
+ broke any attempt to instantiate it outside a fully booted application. It now imports the
1475
+ button by its concrete path.
1476
+
1477
+ #### [1.4.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.4.2) - 2026-09-20
1478
+
1479
+ ##### Fixed
1480
+
1481
+ - Declared `rxjs` in `peerDependencies`; it is used throughout the package but was never declared.
1482
+ - Added a README to the published package.
1483
+
1484
+ ##### ⚠ Breaking Changes / Migration
1485
+
1486
+ **Requires Angular 22.** Every `@angular/*` peer range moved from `^19.1.0` to `^22.0.0`, and
1487
+ the package is built and tested against Angular 22.1 with TypeScript 6.0.
1488
+
1489
+ Upgrade your application to Angular 22 first — Angular does not support skipping majors, so go
1490
+ 19 → 20 → 21 → 22 with `ng update`. Angular 22 also requires Node.js 22.22.3 or newer.
1491
+
1492
+ **Requires `@zambon-dev/library` 2.x.** The peer range moved from `^1.6.0` to `^2.0.0`. See the
1493
+ library changelog for the `FormInputComponent` output renames; if your application binds those
1494
+ outputs directly, apply that migration too.
1495
+
1496
+ #### [1.4.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.4.1) - 2026-09-19
1497
+
1498
+ ##### Fixed
1499
+
1500
+ - **A details tab with child views no longer loses its ribbon after you visit another tab and come
1501
+ back.** Every button of the child view disappeared -- filter, clear filters, refresh, export --
1502
+ while the grid below carried on showing its filtered rows, and nothing brought them back short of
1503
+ switching views.
1504
+
1505
+ A child view publishes its ribbon from `ngAfterViewInit`, and the router only names the active
1506
+ view afterwards, so the first template of a tab was cached under an empty id. Looking it up later
1507
+ under the real name missed and emptied the ribbon. It never recovered because a tab being
1508
+ re-activated has its child **re-attached** rather than re-created, so `ngAfterViewInit` does not
1509
+ run again and nothing publishes a second time.
1510
+
1511
+ A list screen was unaffected: it has no view switching, so the lookup never happened.
1512
+
1513
+ #### [1.4.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.4.0) - 2026-09-16
1514
+
1515
+ ##### Changed
1516
+
1517
+ - **`framework-button-filters` no longer submits the labels of catalog selections.** A filters
1518
+ form reads its values straight off the form, so the display control `lib-catalog-select` adds
1519
+ for its own use went to the backend beside the real filters — a request filtering by employee
1520
+ carried `employeeName: "753 - ADEMILSON LOPES MAGALHAES"` next to `employeeID: 125`.
1521
+
1522
+ They were ignored, being filters no service declares, but the day one does filter by a name it
1523
+ would receive the formatted label rather than the stored value and quietly match nothing.
1524
+
1525
+ The labels are still **kept** for the modal: reopening it patches them back into the form, and a
1526
+ catalog select backed by a `searchEndpoint` cannot recover its text from the identifier alone —
1527
+ it only resolves a display out of a local entries list. Dropping them from what is stored would
1528
+ have left the field showing a selection with nothing written in it.
1529
+
1530
+ ##### ⚠ Breaking Changes / Migration
1531
+
1532
+ - **Upgrade `@zambon-dev/library` together with this release.** The peer range moved from
1533
+ `^1.0.0` to `^1.6.0`, because `framework-button-filters` now imports `DisplayControls` from it.
1534
+ On an older library that export does not exist, and the filters button fails — at build time if
1535
+ the bundler checks exports, otherwise the first time a filter is submitted. The old range would
1536
+ have let npm resolve that combination without a word of warning.
1537
+
1538
+ - **A backend no longer receives the label of a catalog selection.** If one of your services
1539
+ filters by a key that a `lib-catalog-select` uses as its `displayControlName` — the `xxxName`
1540
+ that comes paired with an `xxxID` — that filter now arrives empty and the query stops narrowing,
1541
+ silently returning more rows than before rather than failing.
1542
+
1543
+ To check, take each filters form and, for every `lib-catalog-select` in it, note the
1544
+ `displayControlName`. Then look for a `TryFilter` on that name in the service behind the list.
1545
+ Any hit has to move to the identifier instead: the label was never the stored value — it is what
1546
+ the catalog chose to display, so filtering by it was already matching on formatting.
1547
+
1548
+ #### [1.3.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.3.1) - 2026-09-10
1549
+
1550
+ ##### Fixed
1551
+
1552
+ - **Returning to an already-open tab no longer crashes with `Maximum call stack size exceeded`.**
1553
+ It affected any screen whose route nests two empty-path levels — the shape every list screen uses:
1554
+ `path: ''` with `DefaultTabViewComponent`, and a child `path: ''` with the list component.
1555
+
1556
+ `CustomReuseStrategy` keyed its detached-view cache on `route.component.name`, and a production
1557
+ build renames every class: esbuild wraps each component as `X = (() => { class i { } return i; })()`,
1558
+ so the name is one mangled letter that the whole chunk shares. An empty path contributes no URL
1559
+ segment, so both levels also resolved to the same URL — which left the two cache keys identical.
1560
+ Storing one handle overwrote the other, both route levels were then handed the same detached view,
1561
+ and Angular blew the stack building a router state whose node was its own descendant.
1562
+
1563
+ The key is now built from the component's **identity** and the route's depth rather than its name.
1564
+ Applications need to change nothing. Worth knowing when reading old reports of this crash: it only
1565
+ ever reproduced in a minified build — `ng serve` keeps real class names — and only on the screens
1566
+ whose chunk happened to mangle to the same letter as the framework's, so a rebuild could move the
1567
+ symptom from one screen to another.
1568
+
1569
+ #### [1.3.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.3.0) - 2026-07-30
1570
+
1571
+ ##### Added
1572
+
1573
+ - `framework-button-export` now accepts a **`defaultOption` `@Input()`** — the index into `options`
1574
+ exported when the main button itself is clicked, defaulting to `0` (Excel). This mirrors the input
1575
+ `framework-button-save` already had; `-1` means "no default" and makes the button only toggle its
1576
+ dropdown. See **Changed** below for the behaviour this changes by default.
1577
+
1578
+ ##### Changed
1579
+
1580
+ - **`framework-button-export` now exports on a single click**, to Excel by default, instead of only
1581
+ opening its format menu. `RibbonButtonComponent` treats a `defaultOption` of -1 as "no default" and
1582
+ merely toggles the dropdown; Export never set one, so the common case took two clicks while
1583
+ `framework-button-save` (which sets 0) took one. Export now sets 0 as well, through the new
1584
+ `defaultOption` `@Input()`. The format menu is unchanged and still reachable from the button's
1585
+ dropdown caret, so every format remains available.
1586
+
1587
+ ##### Fixed
1588
+
1589
+ - **Routed list views now fill the available height automatically.** `DefaultTabViewComponent` gave its
1590
+ routed child no layout and `lib-data-grid` has no `flex-grow` of its own, so a grid collapsed to its
1591
+ `rowsToDisplay × rowHeight` minimum with empty space beneath it, and every consuming list component
1592
+ had to repeat the same `:host { flex; flex-grow; overflow: hidden }` + `lib-data-grid { flex-grow }`
1593
+ stylesheet to compensate. `TabViewList` now carries a `framework-view-list` host class — inherited by
1594
+ every subclass — which `DefaultTabViewComponent` styles, so **consuming apps can delete those
1595
+ per-component stylesheets**. Routed screens that do not extend `TabViewList` (forms, dashboards) are
1596
+ unaffected.
1597
+
1598
+ - `framework-default-tab-view` and `framework-default-details-tab-view` no longer log
1599
+ `NG0100: ExpressionChangedAfterItHasBeenCheckedError` on every render in development mode.
1600
+ Both views assigned a placeholder ribbon template from `ngAfterViewInit` — after Angular had
1601
+ already checked the ribbon's `*ngTemplateOutlet` binding — which tripped the dev-mode change
1602
+ detection check one to three times per load. The placeholder rendered no content, so it has
1603
+ been removed and the outlet is now left empty until a view publishes its own `#ribbon`
1604
+ template. The rendered output is unchanged, and ribbon buttons still appear immediately for
1605
+ list and detail screens.
1606
+
1607
+ ##### ⚠ Breaking Changes / Migration
1608
+
1609
+ None — nothing has to change for your app to compile against this release. Two behaviour changes to
1610
+ check, and one optional cleanup:
1611
+
1612
+ - **Absolutely-positioned content inside a routed list view is now clipped.** The full-height layout
1613
+ applied to `framework-view-list` includes `overflow: hidden`, so a popover, dropdown or tooltip that
1614
+ a list screen renders with `position: absolute` inside its own host is now cut off at the host's
1615
+ edges. Apps that had already written the equivalent `:host { overflow: hidden }` stylesheet by hand
1616
+ are unaffected — the clipping was already in place there. Only list screens that had no such
1617
+ stylesheet change. Fix by portaling the content out of the host (CDK Overlay, as `lib-catalog-select`
1618
+ does) rather than relying on `position: absolute`.
1619
+ - If you relied on `framework-button-export`'s first click opening the format menu rather than
1620
+ exporting, set `[defaultOption]="-1"` to restore that behaviour.
1621
+ - _Optional cleanup:_ components extending `TabViewList` can now delete their
1622
+ `:host { display: flex; flex-grow: 1; overflow: hidden }` / `lib-data-grid { flex-grow: 1 }`
1623
+ stylesheets — `DefaultTabViewComponent` styles the inherited `framework-view-list` host class.
1624
+
1625
+ #### [1.2.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.2.1) - 2026-07-29
1626
+
1627
+ ##### Fixed
1628
+
1629
+ - Lint configuration only: the library's ESLint selector rules now accept the `framework-` prefix
1630
+ that every `@framework` component already uses. They previously still expected the Angular
1631
+ scaffold's `lib` prefix, so the entire component surface reported
1632
+ `@angular-eslint/component-selector` errors. No selector was renamed, and no published API or
1633
+ runtime behavior changed — nothing to do on upgrade.
1634
+
1635
+ ##### ⚠ Breaking Changes / Migration
1636
+
1637
+ None.
1638
+
1639
+ #### [1.2.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.2.0) - 2026-07-24
1640
+
1641
+ ##### Added
1642
+
1643
+ - `AppConfig` now accepts optional application metadata through a second `options` argument:
1644
+ `appName`, `companyName`, `environment`, `logoUrl`, `version`, `notificationsEnabled`, and
1645
+ `notificationsUrl`. These are consumed by the shared application shell (top bar + navigation).
1646
+ Existing `new AppConfig(baseUrl)` calls continue to work unchanged (`notificationsEnabled`
1647
+ defaults to `false`, other new fields to empty strings).
1648
+
1649
+ ##### ⚠ Breaking Changes / Migration
1650
+
1651
+ None.
1652
+
1653
+ #### [1.1.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.1.1) - 2026-06-20
1654
+
1655
+ ##### Fixed
1656
+
1657
+ - `framework-button-export` now forwards its `iconSize` input to the underlying ribbon button,
1658
+ so setting `iconSize` correctly resizes the export button's icon. Previously the input was
1659
+ ignored and the icon always rendered at the default size.
1660
+
1661
+ #### [1.1.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.1.0) - 2026-06-02
1662
+
1663
+ - Baseline release: the changelog starts being tracked from this version. Earlier history is
1664
+ available via [GitHub Releases](https://github.com/RicardoZambon/ZLibraries/releases) and the
1665
+ `framework-v*` tags.
1666
+
1667
+ ### Shared
1668
+
1669
+ Authentication, layouts, audit-history features and cross-cutting services. Published as `@zambon-dev/shared` through 6.0.4.
1670
+
1671
+ #### [6.0.4](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v6.0.4) - 2026-10-24
1672
+
1673
+ - Maintenance release (no consumer-facing changes were documented).
1674
+
1675
+ #### [6.0.3](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v6.0.3) - 2026-10-24
1676
+
1677
+ ##### Changed
1678
+
1679
+ - **Every component in this package now renders under any change-detection strategy the host
1680
+ application chooses, zoneless included.** This was the last package. The state each one shows
1681
+ is signals: the embedded-content screen, which decides everything after its menu lookup
1682
+ answers; the notifications panel and the language list, both closed from a window click that
1683
+ is no binding of theirs; the user profile, filled on init; the login form's error, spinner and
1684
+ success state; and the audit screens' selected service and operation.
1685
+
1686
+ ##### ⚠ Breaking Changes / Migration
1687
+
1688
+ - **The state these components expose is signals.** Binding to an input from a template is
1689
+ unchanged. Code that reads one needs `()`:
1690
+
1691
+ | Component | Members |
1692
+ | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
1693
+ | `ServicesHistoryViewComponent` | `controllerName`, `entityID`, `usersCatalogEndpoint` -- `model()`, so setting one still works through `.set(...)` |
1694
+ | `ServicesHistoryChildListComponent` | `selectedServiceID` |
1695
+ | `OperationsHistoryChildListComponent` | `selectedNewValues`, `selectedOldValues` |
1696
+ | `OperationsHistoryModalComponent` | `newValues`, `oldValues` |
1697
+ | `NotificationsComponent` | `dropdown` |
1698
+ | `LanguageSelectorComponent` | `dropdown`, `showFlag`, `title` |
1699
+ | `LoginComponent` | `username` |
1700
+
1701
+ A template reading one through a reference -- `#list` then `list.selectedServiceID` -- needs
1702
+ the call; without it the signal object itself is read, and an object is always truthy.
1703
+
1704
+ - **`ServicesHistoryViewComponent.controllerName` is no longer a required input.** It is a
1705
+ `model()` defaulting to `''`, because the component also fills it from the route's
1706
+ `controllerName` data -- where nothing binds it and `required` never applied. Routed usage is
1707
+ unchanged; a template that binds it is unchanged; a template that forgot to is no longer a
1708
+ compile error, so check yours.
1709
+
1710
+ - **`TopBarComponent.logout` and `ServicesHistoryChildListComponent.filtersChanged` are
1711
+ `output()` rather than `EventEmitter`.** Binding to them is unchanged; calling `.emit()` on
1712
+ one from outside the component no longer compiles, which was never supported anyway.
1713
+
1714
+ #### [6.0.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v6.0.2) - 2026-10-24
1715
+
1716
+ ##### Changed
1717
+
1718
+ - **Internal only: one spec follows `ViewBase`'s `loading` onto a signal.** `@zambon-dev/framework`
1719
+ backs that flag with a signal now, and a spec here stubs the component with `Object.create`,
1720
+ which runs no field initialiser. Nothing in this package's own API changed.
1721
+
1722
+ #### [6.0.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v6.0.1) - 2026-10-24
1723
+
1724
+ ##### Fixed
1725
+
1726
+ - **The peer range on its sibling packages now admits the majors they were released at.** The last
1727
+ release published `@zambon-dev/library` 4.0.0, `@zambon-dev/framework` 5.0.0 and
1728
+ `@zambon-dev/shared` 6.0.0, but the ranges here still asked for the previous majors, so an
1729
+ application installing two of them together got `ERESOLVE`. Nothing else changed.
1730
+
1731
+ ##### ⚠ Breaking Changes / Migration
1732
+
1733
+ - **`OperationsHistoryModalComponent.isModalShown` is a `Signal<boolean>`.** It followed
1734
+ `@zambon-dev/library`'s `ModalComponent.isShown` becoming `isOpen`, and delegates through a
1735
+ signal view query so it answers correctly once that child resolves. Read it as
1736
+ `isModalShown()`. The compiler will not catch a miss here -- reading the signal object itself is
1737
+ valid TypeScript and always truthy -- so grep for `isModalShown` rather than relying on the
1738
+ build.
1739
+
1740
+ #### [6.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v6.0.0) - 2026-10-24
1741
+
1742
+ ##### Changed
1743
+
1744
+ - **`FiltersBase` no longer declares a change-detection strategy**, for the same reason as the
1745
+ framework's view bases: it was never inherited. Your filter components are unaffected.
1746
+
1747
+ ##### Fixed
1748
+
1749
+ - **The services-history author filter appears when its catalog is supplied.** The template read
1750
+ the endpoint through a getter over a plain `@Input()`, which under Angular 22's default strategy
1751
+ raised NG0100 rather than rendering.
1752
+
1753
+ ##### ⚠ Breaking Changes / Migration
1754
+
1755
+ - **`ServicesHistoryFilterComponent.usersCatalogEndpoint` is a signal input.** Binding it from a
1756
+ template -- `[usersCatalogEndpoint]="endpoint"` -- is unchanged. Assigning it directly on a
1757
+ component instance no longer works; use the binding, or `componentRef.setInput` in a test.
1758
+
1759
+ #### [5.0.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v5.0.2) - 2026-09-30
1760
+
1761
+ ##### Changed
1762
+
1763
+ - `UtcDatePipe.transform()` accepts `Date | string`. An API response holds the timestamp as a
1764
+ string even where the model types it as `Date`; existing calls compile unchanged.
1765
+
1766
+ ##### Fixed
1767
+
1768
+ - **`utcDate` no longer moves a timestamp to the previous day east of Greenwich.** It re-read the
1769
+ UTC wall-clock time with hour arithmetic that kept the time of day but, at any positive UTC
1770
+ offset, landed on the day before: `2026-06-15T14:30` showed as June 14 in Madrid (UTC+2). It
1771
+ never showed on CI, which runs in UTC, nor west of Greenwich, where the arithmetic happens to
1772
+ come out right. The pipe now shifts by the zone's offset.
1773
+
1774
+ - **A timestamp that already declares its zone is no longer shifted a second time.** A string
1775
+ ending in `Z` or `±hh:mm` is parsed as the right instant by `Date` itself, so the pipe now
1776
+ returns it as is, and keeps working if an API starts sending the marker.
1777
+
1778
+ - **A value that is not a date returns `null`**, the same as an empty one, instead of an invalid
1779
+ `Date` that makes Angular's `date` pipe throw.
1780
+
1781
+ #### [5.0.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v5.0.1) - 2026-09-26
1782
+
1783
+ ##### Fixed
1784
+
1785
+ - **The operations list no longer keeps a service the services list has dropped.** Reloading the
1786
+ services grid clears its selection but left the last chosen service behind, so the panel beside
1787
+ it went on showing that service’s operations -- including after a filter that no longer lists
1788
+ it. The selection is dropped with the reload now.
1789
+
1790
+ - **The audit date range no longer depends on the server’s time zone.** The two bounds were sent
1791
+ as instants with a trailing `Z`, and `DateTime.TryParse` reads that as an instant and answers
1792
+ with the _machine’s_ local time for it -- so the same request meant different things on a server
1793
+ in UTC and one in UTC-3, while the stored column is plain UTC either way. They are sent as the
1794
+ UTC wall clock now, with no designator.
1795
+
1796
+ - **The peer range on `@zambon-dev/framework` admits 4.x.** 5.0.0 shipped peering `^3.0.0`, hours
1797
+ after framework reached 4.0.0, so installing the two together failed with ERESOLVE. Nothing in
1798
+ this repository could see it -- the workspace resolves siblings from `dist`, so every build,
1799
+ test and Storybook passes with a range no consumer can satisfy. `tools/check-peer-ranges.mjs`
1800
+ now compares each range against the release tags, in PR validation.
1801
+
1802
+ #### [5.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v5.0.0) - 2026-09-26
1803
+
1804
+ ##### Added
1805
+
1806
+ - **Refresh and filters on the audit view.** `ServicesHistoryViewComponent` now contributes a
1807
+ ribbon, with a refresh button and `ServicesHistoryFilterComponent`: the action as text
1808
+ (matched with LIKE), the author, a date range, and whether to list only the operations on the
1809
+ record being audited. The backend side is `AuditFilters` in ZWebAPI; the author filter needs a
1810
+ catalog, which belongs to the application, so it appears only when the view's
1811
+ `usersCatalogEndpoint` is supplied — through the route's data or bound directly.
1812
+
1813
+ The two picked dates are widened to whole local days and sent as UTC instants, because the
1814
+ history stores UTC and the backend compares what it is given: sent as picked, "from the 5th to
1815
+ the 5th" is one instant at midnight and matches nothing.
1816
+
1817
+ ##### ⚠ Breaking Changes / Migration
1818
+
1819
+ - **`ServicesHistoryChildListComponent` no longer provides its own `DataGridDataset`.**
1820
+ `ServicesHistoryViewComponent` provides it instead, so that the ribbon's refresh and filter
1821
+ buttons — which resolve the dataset from their own injector — reach the same instance the grid
1822
+ reads. Nothing changes for the view, which is how this component is meant to be used. Rendering
1823
+ the child list anywhere else now needs the dataset provided above it:
1824
+
1825
+ ```ts
1826
+ providers: [{ provide: DataGridDataset, useClass: ServicesHistoryDataset }],
1827
+ ```
1828
+
1829
+ #### [4.2.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v4.2.2) - 2026-09-26
1830
+
1831
+ ##### Fixed
1832
+
1833
+ - **Component styles are compiled again.** The Tailwind 4 migration left every component's own
1834
+ stylesheet unprocessed in the published bundle, so its `@apply` rules reached the browser as an
1835
+ at-rule the browser does not know and it dropped every rule that used one. In practice the
1836
+ components rendered unstyled in any application installing 4.2.0 or 4.2.1. ng-packagr resolves its
1837
+ PostCSS configuration from the library directory rather than from the workspace root, and the
1838
+ migration added one only at the root. Nothing failed along the way: Storybook compiles from source
1839
+ and never sees the packaged artifact, which is why `tools/check-packaged-styles.mjs` now runs
1840
+ against that artifact in CI and again before every publish.
1841
+
1842
+ #### [4.2.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v4.2.1) - 2026-09-26
1843
+
1844
+ ##### Fixed
1845
+
1846
+ - **`styles/theme.css` is reachable from an application.** It shipped with the Tailwind 4 release
1847
+ but was missing from the `exports` map, so the one supported way to use it —
1848
+ `@reference '@zambon-dev/shared/styles/theme.css'` in a consumer stylesheet — failed the export
1849
+ check and the build stopped. Found on the first application to migrate.
1850
+
1851
+ #### [4.2.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v4.2.0) - 2026-09-25
1852
+
1853
+ ##### Changed
1854
+
1855
+ - **Tailwind CSS 4.** The stylesheets these packages ship are built with Tailwind 4 now. Nothing
1856
+ about the components changed; the framework underneath them did.
1857
+
1858
+ #### [4.1.3](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v4.1.3) - 2026-09-25
1859
+
1860
+ ##### Fixed
1861
+
1862
+ - **The tab panel's drop shadow is no longer sliced off.** The layout's `.content` was
1863
+ `overflow-y-auto`, which made it a clipping box on both axes, and what it clipped was the
1864
+ panel's shadow — 18px of shadow against the 8px it left below. It never needed to scroll:
1865
+ `framework-tabs` is full height and the panel scrolls inside it, measured as
1866
+ `scrollHeight === clientHeight` with the panel in place. Two nested scroll containers where one
1867
+ will do.
1868
+
1869
+ #### [4.1.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v4.1.2) - 2026-09-25
1870
+
1871
+ ##### Fixed
1872
+
1873
+ - **Icons drawn in CSS survive a FontAwesome major.** Four pseudo-elements set
1874
+ `font-family: 'Font Awesome 6 Free'` literally: the sidebar item's expand/collapse chevron and
1875
+ its external-link marker, and the checked-checkbox tick in `form-input` and in the published
1876
+ `styles/components/input.scss`. FontAwesome 7 renames the family to `Font Awesome 7 Free` and,
1877
+ unlike the FontAwesome 5 names, does not keep a 6 alias — so on an application that had moved to
1878
+ v7 the family stopped resolving and every one of those glyphs rendered as a fallback box.
1879
+
1880
+ They now read `var(--fa-family-classic, 'Font Awesome 6 Free')`. FontAwesome 7 defines that
1881
+ variable; 6 does not, so it takes the fallback. The same stylesheet is correct on both, and on
1882
+ 8 as well as long as FontAwesome keeps the variable.
1883
+
1884
+ #### [4.1.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v4.1.1) - 2026-09-25
1885
+
1886
+ ##### Fixed
1887
+
1888
+ - **The peer ranges on `@zambon-dev/library` and `@zambon-dev/framework` now match the versions
1889
+ this package is built against.** Both still asked for `^2.0.0` after those packages moved to 3.x,
1890
+ so installing the three together at their current releases failed `npm install` with ERESOLVE. An
1891
+ application could only get past it with `--legacy-peer-deps` or an override, both of which switch
1892
+ off the peer checking that would have caught a genuine mismatch.
1893
+
1894
+ **Requires Tailwind CSS 4.** An application on 3.x cannot consume this release: `common.scss`
1895
+ references a theme in v4 syntax, and the two versions do not understand each other's directives.
1896
+
1897
+ Tailwind 4 has no JavaScript config — the theme is CSS custom properties — so the `primary` palette
1898
+ that consumers used to declare in their own `tailwind.config.js` now ships here, in
1899
+ `@zambon-dev/shared/styles/theme.css`. That file is the Tailwind entry point and the theme in one.
1900
+
1901
+ In the application:
1902
+
1903
+ 1. Install `tailwindcss@^4` and `@tailwindcss/postcss@^4`, and remove `tailwind.config.js`. A
1904
+ `.postcssrc.json` with `{"plugins": {"@tailwindcss/postcss": {}}}` replaces it.
1905
+
1906
+ 2. Load the theme **before** the shared stylesheet, and load it as CSS rather than through Sass.
1907
+ Sass's `@use` of a plain CSS file drops the `@layer` blocks the theme relies on, silently:
1908
+
1909
+ ```jsonc
1910
+ // angular.json / project.json, styles:
1911
+ "node_modules/@zambon-dev/shared/styles/theme.css",
1912
+ "node_modules/@zambon-dev/shared/styles/common.scss",
1913
+ ```
1914
+
1915
+ 3. Any stylesheet of your own that uses `@apply` needs to say where its theme comes from:
1916
+ `@reference '@zambon-dev/shared/styles/theme.css';`. In 3.x the PostCSS plugin carried the
1917
+ config to every file; in 4 a file is on its own without this.
1918
+
1919
+ 4. If you kept a custom palette in `tailwind.config.js`, move it into a `@theme` block. Colours
1920
+ become `--color-<name>-<shade>`.
1921
+
1922
+ `theme.css` also restores two v3 defaults that Tailwind 4 dropped, because the components were
1923
+ built against them: the default border colour (`gray-200`, not `currentColor`) and the pointer
1924
+ cursor on buttons. Both are marked in the file. Taking either away is a visual decision, not a
1925
+ cleanup.
1926
+
1927
+ **What was verified, and what was not.** `tools/style-snapshot` captures the computed styles of
1928
+ every element in all 43 stories and diffs two builds; the migration was driven by that diff rather
1929
+ than by reading release notes, and it caught three regressions nothing else reported — 615 elements
1930
+ whose borders turned black, 41 buttons that lost their pointer cursor, and a sidebar that rendered
1931
+ collapsed because `@screen md` is a v3 directive that 4 leaves as an unknown at-rule for the browser
1932
+ to drop.
1933
+
1934
+ What remains is 138 differing values in 20 distinct changes, all triaged and none of them obviously
1935
+ wrong: `divide-*` moved its border from the top of one element to the bottom of the previous one,
1936
+ which is the documented v4 selector change; a handful of rings and shadows compose their transparent
1937
+ layers differently; and three elements are 8px shorter. **These want an eye on them before release.**
1938
+ Colour values across the board are the same colours in OKLCH, which is what v4's palette is.
1939
+
1940
+ #### [4.1.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v4.1.0) - 2026-09-21
1941
+
1942
+ ##### Added
1943
+
1944
+ - Added the `--tabs-*` design tokens to the published stylesheet, mirroring the set
1945
+ `@zambon-dev/library` gained in 3.1.0. They style `@zambon-dev/framework`'s tab strip —
1946
+ colours, radii, blur, the shadow, the minimum and maximum tab width, and the width of the
1947
+ scroll controls and edge fades — and they sit alongside `--sidebar-*` in the same layer, so
1948
+ the strip can be retuned without touching the component.
1949
+
1950
+ This release shipped them but originally recorded no entry; the note was added afterwards.
1951
+
1952
+ #### [4.0.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v4.0.1) - 2026-09-21
1953
+
1954
+ ##### Fixed
1955
+
1956
+ - **Host styles now apply.** The services-history view and both history child lists declared their
1957
+ host block as `:host-context {` with no argument, so every rule inside was silently dropped.
1958
+ They are `:host {` now.
1959
+
1960
+ #### [4.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v4.0.0) - 2026-09-21
1961
+
1962
+ - Maintenance release (no consumer-facing changes were documented).
1963
+
1964
+ #### [3.0.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v3.0.1) - 2026-09-21
1965
+
1966
+ ##### Changed
1967
+
1968
+ - `LoginLayoutComponent`, `HomeComponent`, `BrandComponent` and `EnvironmentBadgeComponent` now
1969
+ use `ChangeDetectionStrategy.OnPush`. Behaviour is unchanged; they render from inputs or from
1970
+ immutable application config.
1971
+
1972
+ - **Accessibility.** The operations-history row opens its detail modal from the keyboard as well as
1973
+ the mouse.
1974
+
1975
+ #### [3.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v3.0.0) - 2026-09-21
1976
+
1977
+ ##### Changed
1978
+
1979
+ - **Accessibility.** The operations-history row opens its detail modal from the keyboard as well as
1980
+ the mouse.
1981
+
1982
+ > **The 2.1.x line is retired.** It carried shared for applications still on Angular 19 while
1983
+ > `main` moved to Angular 22, publishing under its own `2.1.x` dist-tag. Its branch is gone; the
1984
+ > final state is kept in the `angular-19/shared-2.1.x` tag. Every fix recorded below also shipped
1985
+ > in a 5.x release, which requires Angular 22.
1986
+
1987
+ #### [2.1.4](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v2.1.4) - 2026-09-30
1988
+
1989
+ ##### Changed
1990
+
1991
+ - `UtcDatePipe.transform()` accepts `Date | string`. An API response holds the timestamp as a
1992
+ string even where the model types it as `Date`; existing calls compile unchanged.
1993
+
1994
+ ##### Fixed
1995
+
1996
+ - **`utcDate` no longer moves a timestamp to the previous day east of Greenwich.** It re-read the
1997
+ UTC wall-clock time with hour arithmetic that kept the time of day but, at any positive UTC
1998
+ offset, landed on the day before: `2026-06-15T14:30` showed as June 14 in Madrid (UTC+2). It
1999
+ never showed on CI, which runs in UTC, nor west of Greenwich, where the arithmetic happens to
2000
+ come out right. The pipe now shifts by the zone's offset.
2001
+
2002
+ - **A timestamp that already declares its zone is no longer shifted a second time.** A string
2003
+ ending in `Z` or `±hh:mm` is parsed as the right instant by `Date` itself, so the pipe now
2004
+ returns it as is, and keeps working if an API starts sending the marker.
2005
+
2006
+ - **A value that is not a date returns `null`**, the same as an empty one, instead of an invalid
2007
+ `Date` that makes Angular's `date` pipe throw.
2008
+
2009
+ #### [2.1.3](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v2.1.3) - 2026-09-21
2010
+
2011
+ - Maintenance release (no consumer-facing changes were documented).
2012
+
2013
+ #### [2.1.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v2.1.2) - 2026-09-20
2014
+
2015
+ ##### Fixed
2016
+
2017
+ - Declared `rxjs` and `@angular/platform-browser` in `peerDependencies`. `DomSanitizer` is used by
2018
+ `ExternalContentComponent` and `BypassHtmlSanitizerPipe`, but neither package was declared.
2019
+ - Added a README to the published package.
2020
+
2021
+ ##### ⚠ Breaking Changes / Migration
2022
+
2023
+ **Requires Angular 22.** Every `@angular/*` peer range moved from `^19.1.0` to `^22.0.0`, and
2024
+ the package is built and tested against Angular 22.1 with TypeScript 6.0.
2025
+
2026
+ Upgrade your application to Angular 22 first — Angular does not support skipping majors, so go
2027
+ 19 → 20 → 21 → 22 with `ng update`. Angular 22 also requires Node.js 22.22.3 or newer.
2028
+
2029
+ **Requires `@zambon-dev/library` 2.x and `@zambon-dev/framework` 2.x.** Both peer ranges moved to
2030
+ `^2.0.0`. See the library changelog for the `FormInputComponent` output renames.
2031
+
2032
+ #### [2.1.1](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v2.1.1) - 2026-09-15
2033
+
2034
+ ##### Fixed
2035
+
2036
+ - **An embedded destination served over `http` now says why it cannot be shown, instead of
2037
+ rendering an empty frame.** A browser refuses to embed an `http://` frame inside an `https://`
2038
+ page — an iframe is active mixed content, and the block is unconditional. Nothing on the page
2039
+ can permit it: no attribute, no header, and not CSP, which only ever restricts further. The
2040
+ frame was mounted anyway, the browser dropped it silently, and after the timeout the screen
2041
+ offered the “still loading” hint, which pointed at the wrong cause.
2042
+
2043
+ Unlike a site refusing to be framed — which is genuinely unknowable from JavaScript — this one
2044
+ is decidable up front, so `ExternalContentComponent` now compares the page’s protocol with the
2045
+ destination’s and shows a message naming the real reason. **Open in a new browser tab keeps
2046
+ working and is the way out:** a top-level navigation to `http://` is not mixed content. For a
2047
+ destination that only speaks `http`, configure the menu item as _external, new tab_ rather than
2048
+ _external, embedded_; to embed it, put it behind an `https` reverse proxy.
2049
+
2050
+ Two new keys, `ExternalContent-Insecure-Title` and `ExternalContent-Insecure-Message`, ship in
2051
+ `en` and `pt`. An application that overrides this feature’s translations needs to add them.
2052
+
2053
+ #### [2.1.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v2.1.0) - 2026-09-08
2054
+
2055
+ ##### Added
2056
+
2057
+ - **External sidebar destinations are now opened, in either of two modes.** `MainLayoutComponent`
2058
+ subscribes to `@zambon-dev/library`’s new `SidebarService.menuExternalUrlSelected`: an item whose
2059
+ `openMode` is `ExternalNewTab` opens in a new browser tab (`noopener,noreferrer`), and one marked
2060
+ `ExternalEmbedded` opens an application tab that displays the destination in a sandboxed iframe,
2061
+ keeping the user inside the application. Internal routes are untouched.
2062
+
2063
+ - **`ExternalUrlResolverService`** — substitutes the runtime placeholders in an external menu URL
2064
+ and vets the result. The placeholder set is closed and case-sensitive: `{email}`, `{language}`,
2065
+ `{userId}`, `{userName}`. Every substituted value is `encodeURIComponent`-ed, so a name containing
2066
+ `&` cannot inject a query parameter — which also means one placeholder must occupy one whole path
2067
+ segment or query-parameter value. A supported placeholder with no value becomes an empty string
2068
+ (with a console warning); an unrecognized `{…}` is left exactly as configured, so a report URL
2069
+ that legitimately contains braces, such as `?filter={"a":1}`, is not corrupted.
2070
+
2071
+ No authentication token is ever substituted, and the set is closed by construction rather than
2072
+ reflected off the stored user info — `AuthenticationService` persists the whole sign-in response
2073
+ under `userInfo`, tokens included, so a reflective implementation would let a URL configured as
2074
+ `?t={token}` hand the JWT to a third party.
2075
+
2076
+ `{language}` and `{userName}` work today. `{userId}` and `{email}` resolve to an empty string
2077
+ until your `Authentication/SignIn` and `Authentication/RefreshToken` responses include `userID`
2078
+ and `email` (see `ICurrentUserInfo` below).
2079
+
2080
+ - **`ExternalContentComponent` + `externalContentRoutes`** — the embedded view. Spread the routes
2081
+ into `MainLayoutComponent`’s children:
2082
+
2083
+ ```ts
2084
+ { path: '', component: MainLayoutComponent, canActivate: [AuthGuard], children: [
2085
+ ...externalContentRoutes,
2086
+ // your own features
2087
+ ] }
2088
+ ```
2089
+
2090
+ Register them even if you only plan to use `ExternalNewTab`: an embedded item configured without
2091
+ them opens a tab that immediately bounces to the home route.
2092
+
2093
+ The view is an ordinary `TabViewBase` hosted by `DefaultTabViewComponent`, so its actions appear
2094
+ in the application ribbon (a **Page** group with **Refresh** and **Open in a new browser tab**)
2095
+ and look like every other screen’s — the shipped `externalContentRoutes` already wires that host
2096
+ up, which is the other reason not to hand-write the routes. **Refresh** carries the same icon and
2097
+ label as `framework-button-refresh`, because reloading a report is the same action as refreshing
2098
+ a grid.
2099
+
2100
+ Refreshing genuinely tears the frame down and builds a new one — a cross-origin frame cannot be
2101
+ navigated any other way — so the old render visibly goes away instead of sitting there while you
2102
+ wonder whether anything happened. The button spins and the panel shows a loading overlay until
2103
+ the destination reports `load`, or until the slow-frame delay elapses, so a destination that
2104
+ never reports one cannot leave the controls stuck.
2105
+
2106
+ **Open in a new browser tab** is always available, because many sites refuse to be embedded
2107
+ (`X-Frame-Options`, CSP `frame-ancestors`) and a browser gives JavaScript no reliable way to
2108
+ detect that — if nothing has loaded after a few seconds the view also shows a hint saying so. An
2109
+ `https` application cannot embed an `http` destination at all; the same button is the way out.
2110
+
2111
+ The tab URL is `/external-content/<menu id>` and never carries the destination, so no one can
2112
+ hand-craft a link that makes your application frame an arbitrary site. Pressing F5 on an embedded
2113
+ tab restores both the frame and the tab title from `sessionStorage`. Opening that URL in a _fresh_
2114
+ browser tab can only work if your `SidebarService.getMenuFromUrl()` resolves
2115
+ `/external-content/<id>`; otherwise the view says the content is unavailable and asks the user to
2116
+ reopen it from the menu.
2117
+
2118
+ - **`EXTERNAL_CONTENT_CONFIGS`** — `allowedOrigins` (default `[]`, meaning any `http`/`https`
2119
+ origin) and `slowFrameHintDelay` (default 5000 ms). **Populate `allowedOrigins` in production.**
2120
+ Embedded and new-tab destinations are already rejected unless they are absolute `http`/`https`
2121
+ URLs, which stops a `javascript:` or `data:` URL in your menu table from executing in your users’
2122
+ session; an origin allowlist narrows what is left from “any site on the internet” to your known
2123
+ report hosts. The iframe is sandboxed with a fixed, non-configurable token list that withholds
2124
+ `allow-top-navigation`, so a framed site cannot navigate your application away. Do not point an
2125
+ embedded item at your own application’s origin — use an internal route for that.
2126
+
2127
+ - **`ICurrentUserInfo.email`, `.userID`, `.username`** (all optional) — the values behind
2128
+ `{email}`, `{userId}` and `{userName}`. `username` was already being persisted in the base64
2129
+ `userInfo` entry and is now simply typed, so `{userName}` needs no backend change; `email` and
2130
+ `userID` must be added to your sign-in and refresh responses.
2131
+
2132
+ - **Translations** for the embedded view under `i18n/external-content/`, already included in
2133
+ `ZAMBON_SHARED_I18N_RESOURCES`.
2134
+
2135
+ ##### Fixed
2136
+
2137
+ - **A deep link whose URL the menu API cannot resolve no longer surfaces an unhandled error.**
2138
+ `MainLayoutComponent` resolves a deep-linked tab’s title through `SidebarService.getMenuFromUrl()`
2139
+ and had no error handler, so a 404 became an unhandled rejection in the console. The title is
2140
+ best-effort and the failure is now swallowed — which matters more now that the embedded-content
2141
+ route is a URL shape most menu endpoints do not know.
2142
+
2143
+ ##### ⚠ Breaking Changes / Migration
2144
+
2145
+ - **Requires `@zambon-dev/library` 1.4.0 or later.** `SidebarMenuOpenMode`,
2146
+ `toSidebarMenuOpenMode` and `SidebarService.menuExternalUrlSelected` ship in that release. The
2147
+ declared peer range still allows older versions, so upgrade both packages together.
2148
+ - **To use external menu items, register `externalContentRoutes`** as children of
2149
+ `MainLayoutComponent` (snippet above), and make your menu endpoint return `openMode`. The
2150
+ resolver accepts it as the camelCase string (`"internal"`, `"externalNewTab"`,
2151
+ `"externalEmbedded"`, matched case-insensitively) or as the enum ordinal (`0`, `1`, `2`), so a
2152
+ plain ASP.NET Core enum property works with no converter.
2153
+ - **The embedded view needs both i18n bundles registered.** Its own strings ship in this package
2154
+ (`ZAMBON_SHARED_I18N_RESOURCES`, under `assets/i18n/zambon-dev/shared/external-content/`), but the
2155
+ ribbon reuses three keys owned by `@zambon-dev/framework` — `RibbonGroup-Page`, `Button-Refresh`
2156
+ and `Loading` — so `ZAMBON_FRAMEWORK_I18N_RESOURCES` has to be registered too, or those render as
2157
+ raw keys. Any application that already uses framework buttons registers it; if your
2158
+ `TranslateLoader` hand-lists prefixes instead of spreading the two constants, add both.
2159
+ - Nothing else changes: applications with no `openMode` on any menu item behave exactly as before
2160
+ and need no action.
2161
+
2162
+ #### [2.0.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v2.0.0) - 2026-07-30
2163
+
2164
+ ##### Added
2165
+
2166
+ - **Storybook: `Shared/App Showcase` story** — a full-height, navigable demo of the complete
2167
+ application shell, backed by in-memory mock data. Clicking the sidebar entries (Dashboard,
2168
+ General ▸ Customers/Units, Security ▸ Users) opens tabs that render working list-views and
2169
+ detail-views through the real hosts (`DefaultTabViewComponent` /
2170
+ `DefaultDetailsTabViewComponent`, `TabViewList` / `FormView`, and the `framework-button-*`
2171
+ ribbon buttons). It covers:
2172
+
2173
+ - a branded top bar (logo, app name, subtitle, environment badge and a working notifications
2174
+ bell) and a versioned sidebar footer;
2175
+ - a mocked audit/history view, reachable from the detail views' Views button;
2176
+ - `framework-button-filters` (via a `FiltersBase` component per entity) and
2177
+ `framework-button-export` on every list: the mock dataset honours `IListParameters.filters`, so
2178
+ filtering visibly narrows the grid, and exporting downloads a real CSV of the filtered rows;
2179
+ - a **child list of addresses** on the Customers detail view, edited through `lib-multi-editor` —
2180
+ `ChildList` and `MultiEditorModal` together, with the accordion gated on `hasEntityID` (a child
2181
+ collection needs a persisted parent), the Edit button opening the multi-editor rather than
2182
+ entering form edit mode, and add/remove/edit applied as one batch. The Customers grid's City
2183
+ column is **derived** from the customer's first address, so editing an address is reflected in
2184
+ the parent list and its filter with nothing to keep in sync;
2185
+ - mocked request latency, so loading states are observable;
2186
+ - full `en`/`pt` translation, so the language selector switches the entire showcase.
2187
+
2188
+ Development-only: it lives entirely in `app-showcase.stories.ts`, which is excluded from the
2189
+ package build.
2190
+
2191
+ ##### Fixed
2192
+
2193
+ - **Audit history models now match the JSON the audit endpoints actually return.**
2194
+ `IServicesHistoryList` and `IOperationsHistoryList` declared an `ID` property, and
2195
+ `IOperationsHistoryList` declared `entityId`. Neither key is ever sent. The audit endpoints
2196
+ (`POST /{controller}/{entityID}/Audit` and `.../Audit/{serviceHistoryID}`) serialize under
2197
+ ASP.NET Core's default camelCase policy, which lowercases only a _leading_ run of capitals — so the
2198
+ backend's `ID` goes out as `id` and its `EntityID` goes out as `entityID`. Typing a row against
2199
+ `.ID` or `.entityId` therefore compiled fine and read `undefined` at run time. See
2200
+ **⚠ Breaking Changes / Migration** below.
2201
+
2202
+ This was also a latent trap for anything supplying its own audit rows: the grid resolves row
2203
+ identity through `compareProperty`, which is `'id'`, so a row carrying only `ID` could never be
2204
+ selected — clicking a service entry left the operations grid empty. Rows shaped like the real
2205
+ payload work unchanged; `@shared`'s own runtime behaviour is not affected by this release.
2206
+
2207
+ - **The top bar's notifications bell is no longer pushed off-centre by its unread badge.** The badge
2208
+ is an absolutely-positioned overlay, so it adds no width — but it is still a DOM sibling after the
2209
+ bell icon, which defeated the `.btn i:not(:last-child)` margin guard in the global button styles.
2210
+ That left 8px of dead space to the icon's right and widened the button from 38px to 46px, but only
2211
+ while an unread count was showing, so it came and went with the count. The margin is now cleared
2212
+ for that button specifically; the shared guard, which is correct for icon-plus-label buttons, is
2213
+ unchanged.
2214
+
2215
+ - **Lint: `shared-` component/directive selector prefix is now accepted.**
2216
+ `libs/shared/eslint.config.mjs` still carried the scaffolded `prefix: 'lib'`, so the library's own
2217
+ `shared-`-prefixed components (`shared-main-layout`, `shared-login-layout`) failed
2218
+ `@angular-eslint/component-selector`. Both selector rules now accept `['lib', 'shared']`.
2219
+
2220
+ ##### ⚠ Breaking Changes / Migration
2221
+
2222
+ Two properties on the audit history models were renamed to the keys the backend actually sends. Both
2223
+ models are exported from the package root, so anything typed against the old names will now fail to
2224
+ compile. Nothing in `@shared` changes behaviour at run time — the fix is to the declared types.
2225
+
2226
+ | Model | Before | After |
2227
+ | ------------------------ | ------------------- | ------------------- |
2228
+ | `IServicesHistoryList` | `ID: number` | `id: number` |
2229
+ | `IOperationsHistoryList` | `ID: number` | `id: number` |
2230
+ | `IOperationsHistoryList` | `entityId?: number` | `entityID?: number` |
2231
+
2232
+ To upgrade:
2233
+
2234
+ 1. Rename `.ID` to `.id` wherever you read or construct an `IServicesHistoryList` or
2235
+ `IOperationsHistoryList`. If the compiler now reports the property as missing, that code was
2236
+ reading `undefined` before — it never matched the payload.
2237
+ 2. Rename `.entityId` to `.entityID` on `IOperationsHistoryList`. The capitals are deliberate and
2238
+ match the backend's `EntityID`; do not "correct" them back.
2239
+ 3. If you supply audit rows yourself — a test double, a Storybook mock, a hand-rolled
2240
+ `ServicesHistoryService` / `OperationsHistoryService` — emit `id`, not `ID`. Rows keyed only on
2241
+ `ID` were never selectable, so a service row's selection could not reach the operations grid.
2242
+ 4. If you worked around that by overriding `compareProperty` to `'ID'` in a `ServicesHistoryDataset`
2243
+ or `OperationsHistoryDataset` subclass, remove the override. The inherited `'id'` is now correct.
2244
+
2245
+ #### [1.2.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v1.2.0) - 2026-07-28
2246
+
2247
+ ##### Added
2248
+
2249
+ - A tokenized app backdrop — `--app-backdrop`, a subtle light-gray gradient — rendered behind the
2250
+ whole layout shell so the sidebar's new translucent "glass" surface (from `@library`) reads against
2251
+ a colored background.
2252
+
2253
+ ##### Changed
2254
+
2255
+ - `MainLayoutComponent` now floats the sidebar in a padded region over the shared `--app-backdrop`
2256
+ gradient (the shell background was a flat gray). This gives the `@library` glass sidebar a colored
2257
+ surface to read against; the content area shares the same subtle backdrop.
2258
+
2259
+ ##### ⚠ Breaking Changes / Migration
2260
+
2261
+ None. The `--app-backdrop` and floating sidebar region are applied automatically by
2262
+ `MainLayoutComponent`; no consumer action is required.
2263
+
2264
+ #### [1.1.0](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v1.1.0) - 2026-07-24
2265
+
2266
+ ##### Added
2267
+
2268
+ - New application top bar (`shared-top-bar`), rendered by `MainLayoutComponent`, composing the
2269
+ brand (app name, company name, optional logo), an environment badge, notifications, the language
2270
+ selector, and the user profile — alongside the existing sidebar-collapse and logout controls.
2271
+ - `NotificationsService` (root-provided) that streams notifications from a **SignalR hub**. It
2272
+ exposes `getNotifications()`, `getUnreadCount()`, `markAsRead()`, `markAllAsRead()`, `start()`,
2273
+ `stop()`, and an `isEnabled` flag. When enabled it connects (authenticated with the current JWT)
2274
+ to `AppConfig.notificationsUrl` and listens for the server's `ReceiveNotifications` push. The top
2275
+ bar drives it — apps do not call it directly.
2276
+ - The notifications feature is toggleable and configurable via `AppConfig.notificationsEnabled` and
2277
+ `AppConfig.notificationsUrl`; when disabled (or no URL is set) the bell is not rendered.
2278
+ - Notifications show an icon, title, and description, and — when a `callToActionUrl` is present —
2279
+ clicking navigates to it (internal routes via the Angular router, external URLs in a new tab).
2280
+ - Individually reusable top-bar components: `BrandComponent`, `EnvironmentBadgeComponent`,
2281
+ `NotificationsComponent`, `UserProfileComponent`, and the composing `TopBarComponent`.
2282
+ - `top-bar` i18n bundle (`en`/`pt`) registered in `ZAMBON_SHARED_I18N_RESOURCES`.
2283
+ - New peer dependency `@microsoft/signalr` (^10.0.0), used by `NotificationsService`.
2284
+ - `LanguageSelectorComponent` gained a `showFlag` input; when `true` the toggle shows the current
2285
+ language's flag on a light-gray button instead of the text label (used by the top bar; the login
2286
+ page keeps the text label).
2287
+ - `MainLayoutComponent` projects the application version (`AppConfig.version`) into the sidebar's
2288
+ footer slot, shown at the bottom of the navigation when set.
2289
+
2290
+ ##### Changed
2291
+
2292
+ - `ICurrentUserInfo` gained optional `pictureUrl` and `position` fields, displayed in the top-bar
2293
+ user profile. The avatar falls back to the user's initials when `pictureUrl` is absent, and the
2294
+ position line is hidden when `position` is empty.
2295
+ - **`INotification` redefined** to `{ title, description, icon, callToActionUrl?, isRead }` (was
2296
+ `{ id, title, message, read, createdAt }`) to match the SignalR hub contract.
2297
+ - The top bar no longer renders a sidebar-collapse button — the sidebar owns its own collapse/expand
2298
+ toggle. The user profile now uses a light, on-palette surface (neutral chip + brand-colored avatar)
2299
+ instead of the previous dark chip.
2300
+ - On small screens (< 768px) the top bar hides the brand text (app name + company; the logo remains)
2301
+ and the entire user profile, for a compact layout.
2302
+ - `LanguageSelectorComponent` now renders language flags as self-contained inline SVGs (US for `en`,
2303
+ Brazil for `pt`) instead of `/flags/*.png` background images, in both the flag toggle and the
2304
+ dropdown. The selector no longer depends on app-provided flag assets; apps that supplied custom
2305
+ `/flags/*.png` images will no longer see them in the language selector.
2306
+
2307
+ ##### ⚠ Breaking Changes / Migration
2308
+
2309
+ - **`INotification` shape changed** (see above). If you referenced the old fields (`id`, `message`,
2310
+ `read`, `createdAt`), update to `title`, `description`, `icon`, `callToActionUrl`, `isRead`.
2311
+ - **Notifications now require `@microsoft/signalr`.** Install it in the consuming app
2312
+ (`npm i @microsoft/signalr`) — it is a peer dependency.
2313
+
2314
+ To populate the top bar, provide `appName`, `companyName`, `environment`, and optionally `logoUrl`
2315
+ via `AppConfig` options (requires `@zambon-dev/framework` with the new `AppConfig` options). The
2316
+ environment badge maps `DEV`/`QA`/`STG` to colors, renders any other non-empty value with a neutral
2317
+ style, and is hidden for `PROD` or when no environment is set.
2318
+
2319
+ To enable notifications, set `notificationsEnabled: true` and `notificationsUrl` (the SignalR hub
2320
+ URL) in `AppConfig`, and implement a hub that pushes the notification list to clients via a
2321
+ `ReceiveNotifications` invocation (and optionally handles a `MarkAllAsRead` invoke). See
2322
+ `docs/guides/top-bar-configuration.md` for the full consuming-app guide. The user's
2323
+ `pictureUrl`/`position` are supplied by the auth response when available.
2324
+
2325
+ #### [1.0.2](https://github.com/RicardoZambon/ZLibraries/releases/tag/shared-v1.0.2) - 2026-05-02
2326
+
2327
+ - Baseline release: the changelog starts being tracked from this version. Earlier history is
2328
+ available via [GitHub Releases](https://github.com/RicardoZambon/ZLibraries/releases) and the
2329
+ `shared-v*` tags.