@real-router/angular 0.9.0 → 0.11.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.
package/README.md CHANGED
@@ -428,7 +428,7 @@ Navigation directive for `<a>` elements. Handles click events, sets `href`, and
428
428
  <a [realLink]="'settings'" [hash]="'account'">Account</a>
429
429
  ```
430
430
 
431
- Active class is hash-aware — only the matching tab lights up. Live demo: [`examples/web/react/link-hash/`](../../examples/web/react/link-hash/) — behavior is identical across adapters, only template syntax differs. See the [Hash Fragment Support](https://github.com/greydragon888/real-router/wiki/Hash) wiki page for the full surface.
431
+ Active class is hash-aware — only the matching tab lights up. Live demo: [`examples/web/react/hash-examples/link-hash/`](../../examples/web/react/hash-examples/link-hash/) — behavior is identical across adapters, only template syntax differs. See the [Hash Fragment Support](https://github.com/greydragon888/real-router/wiki/Hash) wiki page for the full surface.
432
432
 
433
433
  ### `[realLinkActive]`
434
434
 
@@ -528,11 +528,32 @@ bootstrapApplication(AppComponent, {
528
528
  ```typescript
529
529
  interface RealRouterOptions {
530
530
  scrollRestoration?: ScrollRestorationOptions; // { mode?, anchorScrolling?, scrollContainer? }
531
+ scrollSpy?: ScrollSpyOptions; // { selector, rootMargin?, scrollContainer? } — #575
531
532
  viewTransitions?: boolean;
532
533
  }
533
534
  ```
534
535
 
535
- Restores scroll on back/forward, scrolls to top (or `#hash`) on push. Three modes: `"restore"` (default), `"top"`, `"native"`. Custom containers via `scrollContainer: () => HTMLElement | null`. The utility is created by `provideEnvironmentInitializer` and torn down via `inject(DestroyRef)`. Options are a snapshot at bootstrap — not reactive to runtime changes. See [Scroll Restoration guide](https://github.com/greydragon888/real-router/wiki/Scroll-Restoration) for details.
536
+ Restores scroll on back/forward, scrolls to top (or `#hash`) on push. Three modes: `"restore"` (default), `"top"`, `"native"`. Custom containers via `scrollContainer: () => HTMLElement | null`. The utility is created by `provideEnvironmentInitializer` and torn down via `inject(DestroyRef)`. Options are a snapshot at bootstrap — not reactive to runtime changes. Under `@real-router/browser-plugin`, replace transitions now preserve scroll position and programmatic reloads restore from `sessionStorage` (portable via `state.transition.replace` / `state.transition.reload`). See [Scroll Restoration guide](https://github.com/greydragon888/real-router/wiki/Scroll-Restoration) for the full behaviour matrix.
537
+
538
+ ## Scroll Spy
539
+
540
+ Opt into router-coordinated `IntersectionObserver`-driven URL hash spy via the same options bag:
541
+
542
+ ```typescript
543
+ bootstrapApplication(AppComponent, {
544
+ providers: [
545
+ provideRealRouter(router, {
546
+ scrollSpy: { selector: "[id]:is(h2,h3)" },
547
+ }),
548
+ ],
549
+ });
550
+ ```
551
+
552
+ The URL hash tracks the topmost visible anchor as the user scrolls, syncing `state.context.url.hash` so sibling `<a realLink [hash]>` highlights stay current. Emits a forced same-route transition with `{ hash, replace: true, force: true, hashChange: true }` — same write API as `<a realLink [hash]>` (#532), `replace: true` so spy doesn't pollute history. Anti-flicker gates: `isTransitioning` (skip emits during transitions), `coolingDown` (skip emits during smooth `scrollIntoView` after a hash click; cleared on `scrollend` or 500 ms timeout), `selfEmitting` (spy doesn't rate-limit itself). Hardcoded internals: rAF + 150 ms trailing debounce, MutationObserver re-observe debounced 250 ms.
553
+
554
+ Options: `{ selector: string, rootMargin?: string, scrollContainer?: () => HTMLElement | null }`. Default `rootMargin`: `"-20% 0px -60% 0px"`. Empty `selector` / `undefined` = off. SSR / browsers without `IntersectionObserver` = NOOP. Requires `browser-plugin` or `navigation-plugin` (hash-plugin / memory-plugin → warn-once + NOOP). The utility is installed via `provideEnvironmentInitializer` (`installScrollSpy`) and torn down on `DestroyRef`; options are a snapshot at bootstrap, not reactive.
555
+
556
+ Available on both `provideRealRouter(router, { scrollSpy })` (SPA) and `provideRealRouterFactory({ baseRouter, scrollSpy })` (SSR). On the SSR path the utility is correctly NOOP'd on the server pass (`document` is undefined). Behaviour is identical to the React adapter — see the [React Scroll Spy demo](../../examples/web/react/hash-examples/scroll-spy/) (12 sections, TOC sidebar, 10 e2e scenarios) and the [Scroll Spy guide](https://github.com/greydragon888/real-router/wiki/Scroll-Spy).
536
557
 
537
558
  ## Server-Side Rendering
538
559
 
@@ -707,7 +728,7 @@ Full documentation: [Wiki](https://github.com/greydragon888/real-router/wiki)
707
728
 
708
729
  The shared (cross-framework) wiki pages use the `use*` naming convention — they cover every adapter (React, Preact, Solid, Vue, Svelte, Angular) and each page has an explicit Angular section showing the `inject*` form:
709
730
 
710
- - [RouterProvider](https://github.com/greydragon888/real-router/wiki/RouterProvider) · [RouteView](https://github.com/greydragon888/real-router/wiki/RouteView) · [RouterErrorBoundary](https://github.com/greydragon888/real-router/wiki/RouterErrorBoundary) · [Scroll Restoration](https://github.com/greydragon888/real-router/wiki/Scroll-Restoration)
731
+ - [RouterProvider](https://github.com/greydragon888/real-router/wiki/RouterProvider) · [RouteView](https://github.com/greydragon888/real-router/wiki/RouteView) · [RouterErrorBoundary](https://github.com/greydragon888/real-router/wiki/RouterErrorBoundary) · [Scroll Restoration](https://github.com/greydragon888/real-router/wiki/Scroll-Restoration) · [Scroll Spy](https://github.com/greydragon888/real-router/wiki/Scroll-Spy)
711
732
  - [useRouter → `injectRouter`](https://github.com/greydragon888/real-router/wiki/useRouter) · [useRoute → `injectRoute`](https://github.com/greydragon888/real-router/wiki/useRoute) · [useRouteNode → `injectRouteNode`](https://github.com/greydragon888/real-router/wiki/useRouteNode) · [useNavigator → `injectNavigator`](https://github.com/greydragon888/real-router/wiki/useNavigator) · [useRouteUtils → `injectRouteUtils`](https://github.com/greydragon888/real-router/wiki/useRouteUtils) · [useRouterTransition → `injectRouterTransition`](https://github.com/greydragon888/real-router/wiki/useRouterTransition) · [useRouteExit → `injectRouteExit`](https://github.com/greydragon888/real-router/wiki/useRouteExit) · [useRouteEnter → `injectRouteEnter`](https://github.com/greydragon888/real-router/wiki/useRouteEnter)
712
733
 
713
734
  ## Related Packages
package/dist/README.md CHANGED
@@ -428,7 +428,7 @@ Navigation directive for `<a>` elements. Handles click events, sets `href`, and
428
428
  <a [realLink]="'settings'" [hash]="'account'">Account</a>
429
429
  ```
430
430
 
431
- Active class is hash-aware — only the matching tab lights up. Live demo: [`examples/web/react/link-hash/`](../../examples/web/react/link-hash/) — behavior is identical across adapters, only template syntax differs. See the [Hash Fragment Support](https://github.com/greydragon888/real-router/wiki/Hash) wiki page for the full surface.
431
+ Active class is hash-aware — only the matching tab lights up. Live demo: [`examples/web/react/hash-examples/link-hash/`](../../examples/web/react/hash-examples/link-hash/) — behavior is identical across adapters, only template syntax differs. See the [Hash Fragment Support](https://github.com/greydragon888/real-router/wiki/Hash) wiki page for the full surface.
432
432
 
433
433
  ### `[realLinkActive]`
434
434
 
@@ -528,11 +528,32 @@ bootstrapApplication(AppComponent, {
528
528
  ```typescript
529
529
  interface RealRouterOptions {
530
530
  scrollRestoration?: ScrollRestorationOptions; // { mode?, anchorScrolling?, scrollContainer? }
531
+ scrollSpy?: ScrollSpyOptions; // { selector, rootMargin?, scrollContainer? } — #575
531
532
  viewTransitions?: boolean;
532
533
  }
533
534
  ```
534
535
 
535
- Restores scroll on back/forward, scrolls to top (or `#hash`) on push. Three modes: `"restore"` (default), `"top"`, `"native"`. Custom containers via `scrollContainer: () => HTMLElement | null`. The utility is created by `provideEnvironmentInitializer` and torn down via `inject(DestroyRef)`. Options are a snapshot at bootstrap — not reactive to runtime changes. See [Scroll Restoration guide](https://github.com/greydragon888/real-router/wiki/Scroll-Restoration) for details.
536
+ Restores scroll on back/forward, scrolls to top (or `#hash`) on push. Three modes: `"restore"` (default), `"top"`, `"native"`. Custom containers via `scrollContainer: () => HTMLElement | null`. The utility is created by `provideEnvironmentInitializer` and torn down via `inject(DestroyRef)`. Options are a snapshot at bootstrap — not reactive to runtime changes. Under `@real-router/browser-plugin`, replace transitions now preserve scroll position and programmatic reloads restore from `sessionStorage` (portable via `state.transition.replace` / `state.transition.reload`). See [Scroll Restoration guide](https://github.com/greydragon888/real-router/wiki/Scroll-Restoration) for the full behaviour matrix.
537
+
538
+ ## Scroll Spy
539
+
540
+ Opt into router-coordinated `IntersectionObserver`-driven URL hash spy via the same options bag:
541
+
542
+ ```typescript
543
+ bootstrapApplication(AppComponent, {
544
+ providers: [
545
+ provideRealRouter(router, {
546
+ scrollSpy: { selector: "[id]:is(h2,h3)" },
547
+ }),
548
+ ],
549
+ });
550
+ ```
551
+
552
+ The URL hash tracks the topmost visible anchor as the user scrolls, syncing `state.context.url.hash` so sibling `<a realLink [hash]>` highlights stay current. Emits a forced same-route transition with `{ hash, replace: true, force: true, hashChange: true }` — same write API as `<a realLink [hash]>` (#532), `replace: true` so spy doesn't pollute history. Anti-flicker gates: `isTransitioning` (skip emits during transitions), `coolingDown` (skip emits during smooth `scrollIntoView` after a hash click; cleared on `scrollend` or 500 ms timeout), `selfEmitting` (spy doesn't rate-limit itself). Hardcoded internals: rAF + 150 ms trailing debounce, MutationObserver re-observe debounced 250 ms.
553
+
554
+ Options: `{ selector: string, rootMargin?: string, scrollContainer?: () => HTMLElement | null }`. Default `rootMargin`: `"-20% 0px -60% 0px"`. Empty `selector` / `undefined` = off. SSR / browsers without `IntersectionObserver` = NOOP. Requires `browser-plugin` or `navigation-plugin` (hash-plugin / memory-plugin → warn-once + NOOP). The utility is installed via `provideEnvironmentInitializer` (`installScrollSpy`) and torn down on `DestroyRef`; options are a snapshot at bootstrap, not reactive.
555
+
556
+ Available on both `provideRealRouter(router, { scrollSpy })` (SPA) and `provideRealRouterFactory({ baseRouter, scrollSpy })` (SSR). On the SSR path the utility is correctly NOOP'd on the server pass (`document` is undefined). Behaviour is identical to the React adapter — see the [React Scroll Spy demo](../../examples/web/react/hash-examples/scroll-spy/) (12 sections, TOC sidebar, 10 e2e scenarios) and the [Scroll Spy guide](https://github.com/greydragon888/real-router/wiki/Scroll-Spy).
536
557
 
537
558
  ## Server-Side Rendering
538
559
 
@@ -707,7 +728,7 @@ Full documentation: [Wiki](https://github.com/greydragon888/real-router/wiki)
707
728
 
708
729
  The shared (cross-framework) wiki pages use the `use*` naming convention — they cover every adapter (React, Preact, Solid, Vue, Svelte, Angular) and each page has an explicit Angular section showing the `inject*` form:
709
730
 
710
- - [RouterProvider](https://github.com/greydragon888/real-router/wiki/RouterProvider) · [RouteView](https://github.com/greydragon888/real-router/wiki/RouteView) · [RouterErrorBoundary](https://github.com/greydragon888/real-router/wiki/RouterErrorBoundary) · [Scroll Restoration](https://github.com/greydragon888/real-router/wiki/Scroll-Restoration)
731
+ - [RouterProvider](https://github.com/greydragon888/real-router/wiki/RouterProvider) · [RouteView](https://github.com/greydragon888/real-router/wiki/RouteView) · [RouterErrorBoundary](https://github.com/greydragon888/real-router/wiki/RouterErrorBoundary) · [Scroll Restoration](https://github.com/greydragon888/real-router/wiki/Scroll-Restoration) · [Scroll Spy](https://github.com/greydragon888/real-router/wiki/Scroll-Spy)
711
732
  - [useRouter → `injectRouter`](https://github.com/greydragon888/real-router/wiki/useRouter) · [useRoute → `injectRoute`](https://github.com/greydragon888/real-router/wiki/useRoute) · [useRouteNode → `injectRouteNode`](https://github.com/greydragon888/real-router/wiki/useRouteNode) · [useNavigator → `injectNavigator`](https://github.com/greydragon888/real-router/wiki/useNavigator) · [useRouteUtils → `injectRouteUtils`](https://github.com/greydragon888/real-router/wiki/useRouteUtils) · [useRouterTransition → `injectRouterTransition`](https://github.com/greydragon888/real-router/wiki/useRouterTransition) · [useRouteExit → `injectRouteExit`](https://github.com/greydragon888/real-router/wiki/useRouteExit) · [useRouteEnter → `injectRouteEnter`](https://github.com/greydragon888/real-router/wiki/useRouteEnter)
712
733
 
713
734
  ## Related Packages
@@ -11,8 +11,8 @@ class ClientOnly {
11
11
  this.mounted.set(true);
12
12
  });
13
13
  }
14
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ClientOnly, deps: [], target: i0.ɵɵFactoryTarget.Component });
15
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: ClientOnly, isStandalone: true, selector: "client-only", inputs: { fallback: { classPropertyName: "fallback", publicName: "fallback", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
14
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: ClientOnly, deps: [], target: i0.ɵɵFactoryTarget.Component });
15
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.13", type: ClientOnly, isStandalone: true, selector: "client-only", inputs: { fallback: { classPropertyName: "fallback", publicName: "fallback", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
16
16
  @if (mounted()) {
17
17
  <ng-content />
18
18
  } @else if (fallback()) {
@@ -20,7 +20,7 @@ class ClientOnly {
20
20
  }
21
21
  `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
22
22
  }
23
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ClientOnly, decorators: [{
23
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: ClientOnly, decorators: [{
24
24
  type: Component,
25
25
  args: [{
26
26
  selector: "client-only",
@@ -43,8 +43,8 @@ class ServerOnly {
43
43
  this.mounted.set(true);
44
44
  });
45
45
  }
46
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ServerOnly, deps: [], target: i0.ɵɵFactoryTarget.Component });
47
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: ServerOnly, isStandalone: true, selector: "server-only", inputs: { fallback: { classPropertyName: "fallback", publicName: "fallback", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
46
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: ServerOnly, deps: [], target: i0.ɵɵFactoryTarget.Component });
47
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.13", type: ServerOnly, isStandalone: true, selector: "server-only", inputs: { fallback: { classPropertyName: "fallback", publicName: "fallback", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
48
48
  @if (!mounted()) {
49
49
  <ng-content />
50
50
  } @else if (fallback()) {
@@ -52,7 +52,7 @@ class ServerOnly {
52
52
  }
53
53
  `, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
54
54
  }
55
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: ServerOnly, decorators: [{
55
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: ServerOnly, decorators: [{
56
56
  type: Component,
57
57
  args: [{
58
58
  selector: "server-only",
@@ -182,10 +182,10 @@ class HttpStatusCode {
182
182
  this.sink.code = value;
183
183
  }
184
184
  }
185
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: HttpStatusCode, deps: [], target: i0.ɵɵFactoryTarget.Component });
186
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.9", type: HttpStatusCode, isStandalone: true, selector: "http-status-code", inputs: { code: { classPropertyName: "code", publicName: "code", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "", isInline: true });
185
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: HttpStatusCode, deps: [], target: i0.ɵɵFactoryTarget.Component });
186
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.13", type: HttpStatusCode, isStandalone: true, selector: "http-status-code", inputs: { code: { classPropertyName: "code", publicName: "code", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "", isInline: true });
187
187
  }
188
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: HttpStatusCode, decorators: [{
188
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: HttpStatusCode, decorators: [{
189
189
  type: Component,
190
190
  args: [{
191
191
  selector: "http-status-code",
@@ -1 +1 @@
1
- {"version":3,"file":"real-router-angular-ssr.mjs","sources":["../../ssr/components/ClientOnly.ts","../../ssr/components/ServerOnly.ts","../../ssr/utils/createHttpStatusSink.ts","../../ssr/components/HttpStatusCode.ts","../../ssr/functions/injectDeferred.ts","../../ssr/functions/provideHttpStatusSink.ts","../../ssr/public_api.ts","../../ssr/real-router-angular-ssr.ts"],"sourcesContent":["import { NgTemplateOutlet } from \"@angular/common\";\nimport { afterNextRender, Component, input, signal } from \"@angular/core\";\n\nimport type { TemplateRef } from \"@angular/core\";\n\n@Component({\n selector: \"client-only\",\n template: `\n @if (mounted()) {\n <ng-content />\n } @else if (fallback()) {\n <ng-container [ngTemplateOutlet]=\"fallback() ?? null\" />\n }\n `,\n imports: [NgTemplateOutlet],\n})\nexport class ClientOnly {\n readonly fallback = input<TemplateRef<unknown>>();\n\n readonly mounted = signal(false);\n\n constructor() {\n afterNextRender(() => {\n this.mounted.set(true);\n });\n }\n}\n","import { NgTemplateOutlet } from \"@angular/common\";\nimport { afterNextRender, Component, input, signal } from \"@angular/core\";\n\nimport type { TemplateRef } from \"@angular/core\";\n\n@Component({\n selector: \"server-only\",\n template: `\n @if (!mounted()) {\n <ng-content />\n } @else if (fallback()) {\n <ng-container [ngTemplateOutlet]=\"fallback() ?? null\" />\n }\n `,\n imports: [NgTemplateOutlet],\n})\nexport class ServerOnly {\n readonly fallback = input<TemplateRef<unknown>>();\n\n readonly mounted = signal(false);\n\n constructor() {\n afterNextRender(() => {\n this.mounted.set(true);\n });\n }\n}\n","import { InjectionToken } from \"@angular/core\";\n\n/**\n * Render-scoped HTTP status sink. Created per request on the server and\n * provided via `provideHttpStatusSink(sink)` (or directly through\n * `{ provide: HTTP_STATUS_SINK, useValue: sink }`). Read after the SSR pass\n * (`renderApplication` / `AngularNodeAppEngine` rendering) to apply the value\n * to the HTTP response.\n *\n * Last write wins: if the rendered tree mounts more than one\n * `<http-status-code [code]=\"N\" />`, the value reflects the last component\n * that ran during the render pass.\n *\n * No-op on the client — `<http-status-code />` injects `HTTP_STATUS_SINK`\n * with `{ optional: true }` and skips the write when no provider is\n * registered, so the same component tree can be hydrated without changing\n * behaviour.\n *\n * Constraints:\n * - **Per-request only.** Don't share a sink across requests; the rendered\n * tree mutates `code` in place. Module-level singletons leak status\n * between concurrent requests.\n * - **Don't `Object.freeze` the sink.** The component writes to `.code`;\n * freezing makes the assignment throw under ESM strict mode.\n * - **Pass through `REQUEST_CONTEXT`, not via `req` properties.** Wire the\n * sink with `angularApp.handle(req, { httpStatusSink })` and read it back\n * in the `HTTP_STATUS_SINK` factory via `inject(REQUEST_CONTEXT)`.\n * `AngularNodeAppEngine` builds a fresh Web `Request` from the\n * `IncomingMessage` and discards every custom property, so attaching to\n * `req` directly silently no-ops.\n */\nexport interface HttpStatusSink {\n code: number | undefined;\n}\n\nexport function createHttpStatusSink(): HttpStatusSink {\n return { code: undefined };\n}\n\n/**\n * DI token for the request-scoped HTTP status sink. Application-side wiring:\n *\n * ```ts\n * import { bootstrapApplication } from \"@angular/platform-browser\";\n * import { provideHttpStatusSink, createHttpStatusSink } from \"@real-router/angular/ssr\";\n *\n * const sink = createHttpStatusSink();\n *\n * await bootstrapApplication(AppRoot, {\n * providers: [\n * provideRealRouterFactory({ ... }),\n * provideHttpStatusSink(sink),\n * ],\n * });\n *\n * response.status(sink.code ?? 200).send(html);\n * ```\n */\nexport const HTTP_STATUS_SINK = new InjectionToken<HttpStatusSink>(\n \"HTTP_STATUS_SINK\",\n);\n","import { Component, inject, input } from \"@angular/core\";\n\nimport { HTTP_STATUS_SINK } from \"../utils/createHttpStatusSink\";\n\nimport type { OnInit } from \"@angular/core\";\n\n/**\n * Render-time HTTP status declaration. Mount inside a route component\n * (typical use case: a glob `*` route's NotFound page) when the status is\n * decided by the rendered tree rather than a loader.\n *\n * Writes `code` to the optionally injected `HTTP_STATUS_SINK` in `ngOnInit`\n * (after the input binding has fired) and renders nothing. Without a provider\n * registered (the standard client-side case) the component is a silent no-op\n * — same component tree hydrates without touching the DOM or warning about\n * mismatches.\n *\n * Loader-driven errors (`LoaderNotFound` → 404, `LoaderRedirect` → 30x) keep\n * working as before; this component covers render-time decisions only.\n *\n * Last write wins when several `<http-status-code />` instances mount in the\n * same render pass — sink reflects the last component whose `ngOnInit` ran.\n *\n * ```ts\n * // entry-server.ts\n * import { bootstrapApplication } from \"@angular/platform-browser\";\n * import {\n * createHttpStatusSink,\n * provideHttpStatusSink,\n * } from \"@real-router/angular/ssr\";\n *\n * const sink = createHttpStatusSink();\n * await bootstrapApplication(AppRoot, {\n * providers: [\n * provideRealRouterFactory({ ... }),\n * provideHttpStatusSink(sink),\n * ],\n * });\n * response.status(sink.code ?? 200).send(html);\n * ```\n *\n * ```html\n * <!-- inside not-found.component.ts template -->\n * <http-status-code [code]=\"404\" />\n * ```\n *\n * **Per-request wiring with `AngularNodeAppEngine`:** the sink must be\n * passed via the second arg of `handle(req, requestContext)` — Angular\n * surfaces it through the `REQUEST_CONTEXT` token. Attaching to `req`\n * directly does NOT work: `AngularNodeAppEngine.handle` constructs a fresh\n * Web `Request` from the Express `IncomingMessage` and discards every\n * custom property. See the `ssr/` example's `app.config.ts` factory for\n * the canonical pattern (`inject(REQUEST_CONTEXT, { optional: true })`\n * → `(ctx as { httpStatusSink? } | null)?.httpStatusSink`).\n *\n * **`@angular/ssr` streaming + `@defer` blocks:** `@defer` blocks hydrate\n * lazily on the client; their server-side rendering is fully synchronous,\n * so `<http-status-code />` inside or outside a `@defer` writes to the\n * sink before `AngularNodeAppEngine.handle` resolves. No streaming\n * ordering concern in Angular's current SSR model.\n *\n * **JIT vs AOT:** the `code` input is declared as `input<number>()` (not\n * `input.required<number>()`) because `input.required` trips `NG0950` in\n * JIT/TestBed even after `componentRef.setInput(...)`. `ngOnInit` skips\n * the write when `code()` is `undefined`. AOT (production build) binds\n * the value normally and the skip never fires.\n *\n * **Valid `code` range:** Node's `res.end()` throws `Invalid status code`\n * on `NaN`, `0`, negative values, or values `> 999` — this surfaces as a\n * 5xx / dropped connection, not silent corruption. Pass a real HTTP status\n * integer (commonly 4xx/5xx; 100-999 is what Node accepts).\n */\n@Component({\n selector: \"http-status-code\",\n template: \"\",\n})\nexport class HttpStatusCode implements OnInit {\n /**\n * HTTP status to apply to the response. Common values: 404, 410, 451, 503.\n *\n * Declared as optional so the signal is safe to read in `ngOnInit` under\n * both AOT (template binding fires before init hooks) and JIT/TestBed\n * (`componentRef.setInput(\"code\", N)` writes the value before the first\n * change detection). `input.required` would trip `NG0950` in the JIT path\n * because the required-flag is asserted independently of the runtime\n * value. Consumers should always pass a value — `undefined` makes\n * `ngOnInit` skip the sink write rather than throw.\n */\n readonly code = input<number>();\n\n // Optional injection — when no `provideHttpStatusSink(...)` is registered\n // (client side) the field is null and `ngOnInit` skips the write.\n private readonly sink = inject(HTTP_STATUS_SINK, { optional: true });\n\n ngOnInit(): void {\n if (!this.sink) {\n return;\n }\n\n const value = this.code();\n\n if (value !== undefined) {\n this.sink.code = value;\n }\n }\n}\n","import { computed, effect, signal } from \"@angular/core\";\n\nimport { injectRoute } from \"@real-router/angular\";\n\nimport type { Signal } from \"@angular/core\";\n\ninterface DeferredContext {\n ssrDataDeferred?: Record<string, Promise<unknown>>;\n}\n\nconst NEVER_PROMISE = new Promise<never>(() => {\n // Intentionally never resolves — settles as `undefined` indefinitely when\n // a key is requested that the loader never declared. Surfaces consumer/\n // loader key drift as a visible \"loading\" state in the UI.\n});\n\n/**\n * Read a deferred promise published by `defer({ deferred: { <key>: Promise } })`\n * inside an SSR data loader. Returns an Angular `Signal<T | undefined>` that\n * tracks the active route — re-keying picks up the new state's deferred map.\n *\n * The signal starts `undefined` and updates to the resolved value once the\n * promise settles. Use with native Angular control flow:\n *\n * ```ts\n * @Component({\n * template: `\n * @if (reviews()) {\n * <ul>\n * @for (r of reviews(); track r.id) {\n * <li>{{ r.author }}</li>\n * }\n * </ul>\n * } @else {\n * <p>Loading reviews…</p>\n * }\n * `,\n * })\n * export class Reviews {\n * readonly reviews = injectDeferred<Review[]>(\"reviews\");\n * }\n * ```\n *\n * **Asymmetric Angular** (see `.claude/SSR_FEATURE_GAPS_RU.md` §8): Angular\n * does not ship `<Await>` / `<Streamed>` adapter components — Angular has no\n * direct analogue to React's `use(promise)` or Svelte's `{#await}`. Use\n * `@if (signal()) { … } @else { … }` or the `async` pipe with\n * `from(deferredPromise)` instead.\n */\nexport function injectDeferred<T = unknown>(\n key: string,\n): Signal<T | undefined> {\n const { routeState } = injectRoute();\n\n // Re-derive the promise reference whenever the route changes — invalidate()\n // + reload, navigation to a new route, etc. all refresh the underlying\n // deferred map, and we want the signal to track the *latest* promise.\n const promiseSignal = computed<Promise<T>>(() => {\n const context = routeState().route.context as DeferredContext;\n const deferred = context.ssrDataDeferred;\n\n return (deferred?.[key] ?? NEVER_PROMISE) as Promise<T>;\n });\n\n const value = signal<T | undefined>(undefined);\n\n effect((onCleanup) => {\n const promise = promiseSignal();\n let cancelled = false;\n\n onCleanup(() => {\n cancelled = true;\n });\n\n promise.then(\n (resolved) => {\n if (!cancelled) {\n value.set(resolved);\n }\n },\n /* v8 ignore next 4 -- @preserve: rejection branch — `effect` swallows\n async errors silently, so leaving the signal as `undefined` is the\n only observable behaviour. Real error surfacing is the loader's\n responsibility (throw → navigation rejects → app error boundary). */\n () => {\n // Intentional swallow — see v8 ignore note above.\n },\n );\n });\n\n return value.asReadonly();\n}\n","import { makeEnvironmentProviders } from \"@angular/core\";\n\nimport { HTTP_STATUS_SINK } from \"../utils/createHttpStatusSink\";\n\nimport type { HttpStatusSink } from \"../utils/createHttpStatusSink\";\nimport type { EnvironmentProviders } from \"@angular/core\";\n\n/**\n * Environment providers for a request-scoped `HttpStatusSink`. Pair with\n * `createHttpStatusSink()` and read `sink.code` after the SSR render pass\n * completes.\n *\n * Application bootstrap:\n *\n * ```ts\n * const sink = createHttpStatusSink();\n *\n * await bootstrapApplication(AppRoot, {\n * providers: [\n * provideRealRouterFactory({ ... }),\n * provideHttpStatusSink(sink),\n * ],\n * });\n *\n * response.status(sink.code ?? 200).send(html);\n * ```\n *\n * Equivalent to:\n *\n * ```ts\n * { provide: HTTP_STATUS_SINK, useValue: sink }\n * ```\n *\n * Use the explicit `useValue` form when you need to compose with other\n * application providers in a single `providers: [...]` block.\n */\nexport function provideHttpStatusSink(\n sink: HttpStatusSink,\n): EnvironmentProviders {\n return makeEnvironmentProviders([\n { provide: HTTP_STATUS_SINK, useValue: sink },\n ]);\n}\n","// SSR-feature entry — Angular 21+\n//\n// Server-side and SSR-aware components/functions. Mirrors the\n// `/ssr` subpath split shipped by every other adapter (#604 + #610).\n// Trigger reached: `<ClientOnly>`, `<ServerOnly>`, `injectDeferred()`\n// — three SSR-feature exports, ≥3 threshold per\n// `.claude/SSR_FEATURE_GAPS_RU.md` §8.\n//\n// Asymmetric Angular note: Angular has no native `<Suspense>` /\n// `use(promise)` analogue, so this entry exposes the signal-based\n// `injectDeferred()` instead of `<Await>` / `<Streamed>` adapter\n// components. Consumers compose with `@if (signal()) { … } @else { … }`,\n// the `async` pipe (`from(deferredPromise)`), or native `@defer`\n// blocks for chunk-level lazy hydration.\n\n// Components\nexport { ClientOnly } from \"./components/ClientOnly\";\n\nexport { ServerOnly } from \"./components/ServerOnly\";\n\nexport { HttpStatusCode } from \"./components/HttpStatusCode\";\n\n// Functions\nexport { injectDeferred } from \"./functions/injectDeferred\";\n\nexport { provideHttpStatusSink } from \"./functions/provideHttpStatusSink\";\n\n// Utilities\nexport {\n HTTP_STATUS_SINK,\n createHttpStatusSink,\n} from \"./utils/createHttpStatusSink\";\n\n// Types\nexport type { HttpStatusSink } from \"./utils/createHttpStatusSink\";\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public_api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"real-router-angular-ssr.mjs","sources":["../../ssr/components/ClientOnly.ts","../../ssr/components/ServerOnly.ts","../../ssr/utils/createHttpStatusSink.ts","../../ssr/components/HttpStatusCode.ts","../../ssr/functions/injectDeferred.ts","../../ssr/functions/provideHttpStatusSink.ts","../../ssr/public_api.ts","../../ssr/real-router-angular-ssr.ts"],"sourcesContent":["import { NgTemplateOutlet } from \"@angular/common\";\nimport { afterNextRender, Component, input, signal } from \"@angular/core\";\n\nimport type { TemplateRef } from \"@angular/core\";\n\n@Component({\n selector: \"client-only\",\n template: `\n @if (mounted()) {\n <ng-content />\n } @else if (fallback()) {\n <ng-container [ngTemplateOutlet]=\"fallback() ?? null\" />\n }\n `,\n imports: [NgTemplateOutlet],\n})\nexport class ClientOnly {\n readonly fallback = input<TemplateRef<unknown>>();\n\n readonly mounted = signal(false);\n\n constructor() {\n afterNextRender(() => {\n this.mounted.set(true);\n });\n }\n}\n","import { NgTemplateOutlet } from \"@angular/common\";\nimport { afterNextRender, Component, input, signal } from \"@angular/core\";\n\nimport type { TemplateRef } from \"@angular/core\";\n\n@Component({\n selector: \"server-only\",\n template: `\n @if (!mounted()) {\n <ng-content />\n } @else if (fallback()) {\n <ng-container [ngTemplateOutlet]=\"fallback() ?? null\" />\n }\n `,\n imports: [NgTemplateOutlet],\n})\nexport class ServerOnly {\n readonly fallback = input<TemplateRef<unknown>>();\n\n readonly mounted = signal(false);\n\n constructor() {\n afterNextRender(() => {\n this.mounted.set(true);\n });\n }\n}\n","import { InjectionToken } from \"@angular/core\";\n\n/**\n * Render-scoped HTTP status sink. Created per request on the server and\n * provided via `provideHttpStatusSink(sink)` (or directly through\n * `{ provide: HTTP_STATUS_SINK, useValue: sink }`). Read after the SSR pass\n * (`renderApplication` / `AngularNodeAppEngine` rendering) to apply the value\n * to the HTTP response.\n *\n * Last write wins: if the rendered tree mounts more than one\n * `<http-status-code [code]=\"N\" />`, the value reflects the last component\n * that ran during the render pass.\n *\n * No-op on the client — `<http-status-code />` injects `HTTP_STATUS_SINK`\n * with `{ optional: true }` and skips the write when no provider is\n * registered, so the same component tree can be hydrated without changing\n * behaviour.\n *\n * Constraints:\n * - **Per-request only.** Don't share a sink across requests; the rendered\n * tree mutates `code` in place. Module-level singletons leak status\n * between concurrent requests.\n * - **Don't `Object.freeze` the sink.** The component writes to `.code`;\n * freezing makes the assignment throw under ESM strict mode.\n * - **Pass through `REQUEST_CONTEXT`, not via `req` properties.** Wire the\n * sink with `angularApp.handle(req, { httpStatusSink })` and read it back\n * in the `HTTP_STATUS_SINK` factory via `inject(REQUEST_CONTEXT)`.\n * `AngularNodeAppEngine` builds a fresh Web `Request` from the\n * `IncomingMessage` and discards every custom property, so attaching to\n * `req` directly silently no-ops.\n */\nexport interface HttpStatusSink {\n code: number | undefined;\n}\n\nexport function createHttpStatusSink(): HttpStatusSink {\n return { code: undefined };\n}\n\n/**\n * DI token for the request-scoped HTTP status sink. Application-side wiring:\n *\n * ```ts\n * import { bootstrapApplication } from \"@angular/platform-browser\";\n * import { provideHttpStatusSink, createHttpStatusSink } from \"@real-router/angular/ssr\";\n *\n * const sink = createHttpStatusSink();\n *\n * await bootstrapApplication(AppRoot, {\n * providers: [\n * provideRealRouterFactory({ ... }),\n * provideHttpStatusSink(sink),\n * ],\n * });\n *\n * response.status(sink.code ?? 200).send(html);\n * ```\n */\nexport const HTTP_STATUS_SINK = new InjectionToken<HttpStatusSink>(\n \"HTTP_STATUS_SINK\",\n);\n","import { Component, inject, input } from \"@angular/core\";\n\nimport { HTTP_STATUS_SINK } from \"../utils/createHttpStatusSink\";\n\nimport type { OnInit } from \"@angular/core\";\n\n/**\n * Render-time HTTP status declaration. Mount inside a route component\n * (typical use case: a glob `*` route's NotFound page) when the status is\n * decided by the rendered tree rather than a loader.\n *\n * Writes `code` to the optionally injected `HTTP_STATUS_SINK` in `ngOnInit`\n * (after the input binding has fired) and renders nothing. Without a provider\n * registered (the standard client-side case) the component is a silent no-op\n * — same component tree hydrates without touching the DOM or warning about\n * mismatches.\n *\n * Loader-driven errors (`LoaderNotFound` → 404, `LoaderRedirect` → 30x) keep\n * working as before; this component covers render-time decisions only.\n *\n * Last write wins when several `<http-status-code />` instances mount in the\n * same render pass — sink reflects the last component whose `ngOnInit` ran.\n *\n * ```ts\n * // entry-server.ts\n * import { bootstrapApplication } from \"@angular/platform-browser\";\n * import {\n * createHttpStatusSink,\n * provideHttpStatusSink,\n * } from \"@real-router/angular/ssr\";\n *\n * const sink = createHttpStatusSink();\n * await bootstrapApplication(AppRoot, {\n * providers: [\n * provideRealRouterFactory({ ... }),\n * provideHttpStatusSink(sink),\n * ],\n * });\n * response.status(sink.code ?? 200).send(html);\n * ```\n *\n * ```html\n * <!-- inside not-found.component.ts template -->\n * <http-status-code [code]=\"404\" />\n * ```\n *\n * **Per-request wiring with `AngularNodeAppEngine`:** the sink must be\n * passed via the second arg of `handle(req, requestContext)` — Angular\n * surfaces it through the `REQUEST_CONTEXT` token. Attaching to `req`\n * directly does NOT work: `AngularNodeAppEngine.handle` constructs a fresh\n * Web `Request` from the Express `IncomingMessage` and discards every\n * custom property. See the `ssr/` example's `app.config.ts` factory for\n * the canonical pattern (`inject(REQUEST_CONTEXT, { optional: true })`\n * → `(ctx as { httpStatusSink? } | null)?.httpStatusSink`).\n *\n * **`@angular/ssr` streaming + `@defer` blocks:** `@defer` blocks hydrate\n * lazily on the client; their server-side rendering is fully synchronous,\n * so `<http-status-code />` inside or outside a `@defer` writes to the\n * sink before `AngularNodeAppEngine.handle` resolves. No streaming\n * ordering concern in Angular's current SSR model.\n *\n * **JIT vs AOT:** the `code` input is declared as `input<number>()` (not\n * `input.required<number>()`) because `input.required` trips `NG0950` in\n * JIT/TestBed even after `componentRef.setInput(...)`. `ngOnInit` skips\n * the write when `code()` is `undefined`. AOT (production build) binds\n * the value normally and the skip never fires.\n *\n * **Valid `code` range:** Node's `res.end()` throws `Invalid status code`\n * on `NaN`, `0`, negative values, or values `> 999` — this surfaces as a\n * 5xx / dropped connection, not silent corruption. Pass a real HTTP status\n * integer (commonly 4xx/5xx; 100-999 is what Node accepts).\n */\n@Component({\n selector: \"http-status-code\",\n template: \"\",\n})\nexport class HttpStatusCode implements OnInit {\n /**\n * HTTP status to apply to the response. Common values: 404, 410, 451, 503.\n *\n * Declared as optional so the signal is safe to read in `ngOnInit` under\n * both AOT (template binding fires before init hooks) and JIT/TestBed\n * (`componentRef.setInput(\"code\", N)` writes the value before the first\n * change detection). `input.required` would trip `NG0950` in the JIT path\n * because the required-flag is asserted independently of the runtime\n * value. Consumers should always pass a value — `undefined` makes\n * `ngOnInit` skip the sink write rather than throw.\n */\n readonly code = input<number>();\n\n // Optional injection — when no `provideHttpStatusSink(...)` is registered\n // (client side) the field is null and `ngOnInit` skips the write.\n private readonly sink = inject(HTTP_STATUS_SINK, { optional: true });\n\n ngOnInit(): void {\n if (!this.sink) {\n return;\n }\n\n const value = this.code();\n\n if (value !== undefined) {\n this.sink.code = value;\n }\n }\n}\n","import { computed, effect, signal } from \"@angular/core\";\n\nimport { injectRoute } from \"@real-router/angular\";\n\nimport type { Signal } from \"@angular/core\";\n\ninterface DeferredContext {\n ssrDataDeferred?: Record<string, Promise<unknown>>;\n}\n\nconst NEVER_PROMISE = new Promise<never>(() => {\n // Intentionally never resolves — settles as `undefined` indefinitely when\n // a key is requested that the loader never declared. Surfaces consumer/\n // loader key drift as a visible \"loading\" state in the UI.\n});\n\n/**\n * Read a deferred promise published by `defer({ deferred: { <key>: Promise } })`\n * inside an SSR data loader. Returns an Angular `Signal<T | undefined>` that\n * tracks the active route — re-keying picks up the new state's deferred map.\n *\n * The signal starts `undefined` and updates to the resolved value once the\n * promise settles. Use with native Angular control flow:\n *\n * ```ts\n * @Component({\n * template: `\n * @if (reviews()) {\n * <ul>\n * @for (r of reviews(); track r.id) {\n * <li>{{ r.author }}</li>\n * }\n * </ul>\n * } @else {\n * <p>Loading reviews…</p>\n * }\n * `,\n * })\n * export class Reviews {\n * readonly reviews = injectDeferred<Review[]>(\"reviews\");\n * }\n * ```\n *\n * **Asymmetric Angular** (see `.claude/SSR_FEATURE_GAPS_RU.md` §8): Angular\n * does not ship `<Await>` / `<Streamed>` adapter components — Angular has no\n * direct analogue to React's `use(promise)` or Svelte's `{#await}`. Use\n * `@if (signal()) { … } @else { … }` or the `async` pipe with\n * `from(deferredPromise)` instead.\n */\nexport function injectDeferred<T = unknown>(\n key: string,\n): Signal<T | undefined> {\n const { routeState } = injectRoute();\n\n // Re-derive the promise reference whenever the route changes — invalidate()\n // + reload, navigation to a new route, etc. all refresh the underlying\n // deferred map, and we want the signal to track the *latest* promise.\n const promiseSignal = computed<Promise<T>>(() => {\n const context = routeState().route.context as DeferredContext;\n const deferred = context.ssrDataDeferred;\n\n return (deferred?.[key] ?? NEVER_PROMISE) as Promise<T>;\n });\n\n const value = signal<T | undefined>(undefined);\n\n effect((onCleanup) => {\n const promise = promiseSignal();\n let cancelled = false;\n\n onCleanup(() => {\n cancelled = true;\n });\n\n promise.then(\n (resolved) => {\n if (!cancelled) {\n value.set(resolved);\n }\n },\n /* v8 ignore next 4 -- @preserve: rejection branch — `effect` swallows\n async errors silently, so leaving the signal as `undefined` is the\n only observable behaviour. Real error surfacing is the loader's\n responsibility (throw → navigation rejects → app error boundary). */\n () => {\n // Intentional swallow — see v8 ignore note above.\n },\n );\n });\n\n return value.asReadonly();\n}\n","import { makeEnvironmentProviders } from \"@angular/core\";\n\nimport { HTTP_STATUS_SINK } from \"../utils/createHttpStatusSink\";\n\nimport type { HttpStatusSink } from \"../utils/createHttpStatusSink\";\nimport type { EnvironmentProviders } from \"@angular/core\";\n\n/**\n * Environment providers for a request-scoped `HttpStatusSink`. Pair with\n * `createHttpStatusSink()` and read `sink.code` after the SSR render pass\n * completes.\n *\n * Application bootstrap:\n *\n * ```ts\n * const sink = createHttpStatusSink();\n *\n * await bootstrapApplication(AppRoot, {\n * providers: [\n * provideRealRouterFactory({ ... }),\n * provideHttpStatusSink(sink),\n * ],\n * });\n *\n * response.status(sink.code ?? 200).send(html);\n * ```\n *\n * Equivalent to:\n *\n * ```ts\n * { provide: HTTP_STATUS_SINK, useValue: sink }\n * ```\n *\n * Use the explicit `useValue` form when you need to compose with other\n * application providers in a single `providers: [...]` block.\n */\nexport function provideHttpStatusSink(\n sink: HttpStatusSink,\n): EnvironmentProviders {\n return makeEnvironmentProviders([\n { provide: HTTP_STATUS_SINK, useValue: sink },\n ]);\n}\n","// SSR-feature entry — Angular 21+\n//\n// Server-side and SSR-aware components/functions. Mirrors the\n// `/ssr` subpath split shipped by every other adapter (#604 + #610).\n// Trigger reached: `<ClientOnly>`, `<ServerOnly>`, `injectDeferred()`\n// — three SSR-feature exports, ≥3 threshold per\n// `.claude/SSR_FEATURE_GAPS_RU.md` §8.\n//\n// Asymmetric Angular note: Angular has no native `<Suspense>` /\n// `use(promise)` analogue, so this entry exposes the signal-based\n// `injectDeferred()` instead of `<Await>` / `<Streamed>` adapter\n// components. Consumers compose with `@if (signal()) { … } @else { … }`,\n// the `async` pipe (`from(deferredPromise)`), or native `@defer`\n// blocks for chunk-level lazy hydration.\n\n// Components\nexport { ClientOnly } from \"./components/ClientOnly\";\n\nexport { ServerOnly } from \"./components/ServerOnly\";\n\nexport { HttpStatusCode } from \"./components/HttpStatusCode\";\n\n// Functions\nexport { injectDeferred } from \"./functions/injectDeferred\";\n\nexport { provideHttpStatusSink } from \"./functions/provideHttpStatusSink\";\n\n// Utilities\nexport {\n HTTP_STATUS_SINK,\n createHttpStatusSink,\n} from \"./utils/createHttpStatusSink\";\n\n// Types\nexport type { HttpStatusSink } from \"./utils/createHttpStatusSink\";\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public_api';\n"],"names":[],"mappings":"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"}