@zambon-dev/framework 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -23,6 +23,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
23
23
 
24
24
  ### ⚠ Breaking Changes / Migration
25
25
 
26
+ ## [1.3.1] - 2026-09-10
27
+
28
+ ### Fixed
29
+
30
+ - **Returning to an already-open tab no longer crashes with `Maximum call stack size exceeded`.**
31
+ It affected any screen whose route nests two empty-path levels — the shape every list screen uses:
32
+ `path: ''` with `DefaultTabViewComponent`, and a child `path: ''` with the list component.
33
+
34
+ `CustomReuseStrategy` keyed its detached-view cache on `route.component.name`, and a production
35
+ build renames every class: esbuild wraps each component as `X = (() => { class i { } return i; })()`,
36
+ so the name is one mangled letter that the whole chunk shares. An empty path contributes no URL
37
+ segment, so both levels also resolved to the same URL — which left the two cache keys identical.
38
+ Storing one handle overwrote the other, both route levels were then handed the same detached view,
39
+ and Angular blew the stack building a router state whose node was its own descendant.
40
+
41
+ The key is now built from the component's **identity** and the route's depth rather than its name.
42
+ Applications need to change nothing. Worth knowing when reading old reports of this crash: it only
43
+ ever reproduced in a minified build — `ng serve` keeps real class names — and only on the screens
44
+ whose chunk happened to mangle to the same letter as the framework's, so a rebuild could move the
45
+ symptom from one screen to another.
46
+
26
47
  ## [1.3.0] - 2026-07-30
27
48
 
28
49
  ### Added
@@ -121,7 +142,8 @@ None.
121
142
  available via [GitHub Releases](https://github.com/RicardoZambon/ZLibraries/releases) and the
122
143
  `framework-v*` tags.
123
144
 
124
- [Unreleased]: https://github.com/RicardoZambon/ZLibraries/compare/framework-v1.3.0...HEAD
145
+ [Unreleased]: https://github.com/RicardoZambon/ZLibraries/compare/framework-v1.3.1...HEAD
146
+ [1.3.1]: https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.3.1
125
147
  [1.3.0]: https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.3.0
126
148
  [1.2.1]: https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.2.1
127
149
  [1.2.0]: https://github.com/RicardoZambon/ZLibraries/releases/tag/framework-v1.2.0
@@ -121,6 +121,8 @@ let CustomReuseStrategy = class CustomReuseStrategy {
121
121
  tabService;
122
122
  applicationRef = inject(ApplicationRef);
123
123
  cachedHandles = {};
124
+ componentIDs = new WeakMap();
125
+ nextComponentID = 1;
124
126
  //#endregion
125
127
  //#region Properties
126
128
  //#endregion
@@ -193,8 +195,39 @@ let CustomReuseStrategy = class CustomReuseStrategy {
193
195
  if (this.clones[url]) {
194
196
  url = this.clones[url];
195
197
  }
196
- const componentName = route.component?.name ?? '';
197
- return `${url}-${componentName}`;
198
+ // The component is identified by identity, never by name. A production build renames every
199
+ // class: esbuild wraps each component as `X = (() => { class i { } return i; })()`, so
200
+ // `component.name` is a single mangled letter that the whole chunk shares. Nested empty-path
201
+ // routes already resolve to the same URL, because an empty path contributes no segment, so a
202
+ // key built from the name collapsed a screen's tab view and its list into one entry: storing
203
+ // one overwrote the other, both levels were then handed the same detached view, and Angular
204
+ // blew the stack building a router state whose node was its own descendant. That reproduced
205
+ // only in a minified build, and only on the screens whose chunk happened to mangle to the
206
+ // same letter as the framework's.
207
+ //
208
+ // The depth is part of the key too, so two sibling routes sharing a component type under one
209
+ // URL cannot collide either.
210
+ return `${url}-${this.getRouteDepth(route)}-${this.getComponentID(route.component)}`;
211
+ }
212
+ getComponentID(component) {
213
+ if (typeof component !== 'function') {
214
+ return 0;
215
+ }
216
+ let componentID = this.componentIDs.get(component);
217
+ if (componentID === undefined) {
218
+ componentID = this.nextComponentID++;
219
+ this.componentIDs.set(component, componentID);
220
+ }
221
+ return componentID;
222
+ }
223
+ getRouteDepth(route) {
224
+ let depth = 0;
225
+ let current = route.parent;
226
+ while (current) {
227
+ depth++;
228
+ current = current.parent;
229
+ }
230
+ return depth;
198
231
  }
199
232
  getUrlFromRoute(route) {
200
233
  const segments = [];