@syntrologie/adapt-product 2.29.1 → 2.30.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/dist/cdn.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  onActivate,
3
3
  runtime
4
- } from "./chunk-VY23O32Z.js";
4
+ } from "./chunk-QC5B4ITM.js";
5
5
  import "./chunk-7Z4Y44WC.js";
6
6
 
7
7
  // src/cdn.ts
@@ -208,6 +208,27 @@ var NotifyZ = z.object({
208
208
  icon: z.string().optional()
209
209
  }).nullable().optional();
210
210
 
211
+ // src/presets-store.ts
212
+ function presetsWindow() {
213
+ if (typeof window === "undefined") return void 0;
214
+ return window;
215
+ }
216
+ function getPdpPresets() {
217
+ return presetsWindow()?.__SYNTRO_PDP_PRESETS__;
218
+ }
219
+ function pdpPresetsFetchStarted() {
220
+ const pending2 = presetsWindow()?.__SYNTRO_PDP_PRESETS_PROMISE__;
221
+ return pending2 != null && typeof pending2.then === "function";
222
+ }
223
+ function pdpPresetsReady() {
224
+ const pending2 = presetsWindow()?.__SYNTRO_PDP_PRESETS_PROMISE__;
225
+ if (!pending2 || typeof pending2.then !== "function") return Promise.resolve();
226
+ return Promise.resolve(pending2).then(
227
+ () => void 0,
228
+ () => void 0
229
+ );
230
+ }
231
+
211
232
  // src/scoring/pool-selection.ts
212
233
  var UNKNOWN_DECISION = "unknown";
213
234
  var DISPLAY_MAX = {
@@ -1304,8 +1325,7 @@ function _buildLiveMount(slot, kind, payload) {
1304
1325
  };
1305
1326
  }
1306
1327
  function _readPdpPresets() {
1307
- if (typeof window === "undefined") return void 0;
1308
- return window.__SYNTRO_PDP_PRESETS__;
1328
+ return getPdpPresets();
1309
1329
  }
1310
1330
  function _titleCase(slug) {
1311
1331
  return slug.split(/[-_\s]+/).map((w) => w ? w[0].toUpperCase() + w.slice(1) : "").join(" ");
@@ -1788,6 +1808,26 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement5 {
1788
1808
  this.runtimeRef = null;
1789
1809
  /** Canonical decision and render state. */
1790
1810
  this._decisionState = createInitialPdpState();
1811
+ /**
1812
+ * WI-6b self-identification state. `_selfIdentify` latches true when the
1813
+ * element connects WITHOUT a product-id attr (config-placement shape) —
1814
+ * from then on the page URL, not the attr, owns identity, and SPA
1815
+ * navigations re-resolve it. `_pageUnresolvable` (reactive) makes render()
1816
+ * produce NOTHING — fail-closed, consistent with the missing-preset
1817
+ * behavior: never a broken box on a non-product page.
1818
+ */
1819
+ this._selfIdentify = false;
1820
+ this._pageUnresolvable = false;
1821
+ this._navUnsub = null;
1822
+ /**
1823
+ * WI-4 presets readiness. False while a config-driven `pdpPresetsUrl`
1824
+ * fetch may still land; flips true (reactive) once `pdpPresetsReady()`
1825
+ * settles — which is immediate when no fetch is in flight. Once settled,
1826
+ * an element whose product still has no preset row renders NOTHING
1827
+ * (fail-closed) instead of an eternal loading skeleton.
1828
+ */
1829
+ this._presetsSettled = false;
1830
+ this._presetsReadyWired = false;
1791
1831
  this._composeStartedAt = 0;
1792
1832
  this._lastArchetype = "";
1793
1833
  this._mountDurations = /* @__PURE__ */ new Map();
@@ -1832,12 +1872,22 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement5 {
1832
1872
  const reconnecting = this._decisionState.connection === "disconnected";
1833
1873
  super.connectedCallback();
1834
1874
  ensureSectionLayoutStyles();
1875
+ if (!this.productId) this._selfIdentify = true;
1835
1876
  this._refreshNativeSlots();
1836
1877
  this._syncDecisionRuntime(reconnecting);
1837
1878
  this._syncDecisionPortal();
1879
+ if (!this._presetsReadyWired) {
1880
+ this._presetsReadyWired = true;
1881
+ void pdpPresetsReady().then(() => {
1882
+ this._presetsSettled = true;
1883
+ if (this.isConnected) this._syncDecisionRuntime(false);
1884
+ });
1885
+ }
1838
1886
  }
1839
1887
  disconnectedCallback() {
1840
1888
  super.disconnectedCallback();
1889
+ this._navUnsub?.();
1890
+ this._navUnsub = null;
1841
1891
  for (const handle of this._nativeHandles.values()) handle.restore();
1842
1892
  this._nativeHandles.clear();
1843
1893
  this._dispatchDecision({ type: "disconnected" });
@@ -1880,7 +1930,70 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement5 {
1880
1930
  if (typeof document === "undefined") return null;
1881
1931
  return document.querySelector("[data-syntro-pdp-why-portal]");
1882
1932
  }
1933
+ // ── WI-6b: page-URL self-identification ─────────────────────────────────
1934
+ /**
1935
+ * Resolve identity from the page URL when in self-identify mode. Runs at
1936
+ * every runtime sync (connect + runtimeRef arrival) — idempotent. A miss
1937
+ * marks the page unresolvable: render() then produces NOTHING (fail-closed)
1938
+ * and a warning names the URL. While runtimeRef is still null we stay
1939
+ * quiet — the mountable wires it in moments later and we re-run.
1940
+ */
1941
+ _resolvePageProduct() {
1942
+ if (!this._selfIdentify || !this.runtimeRef) return;
1943
+ const identity = this.runtimeRef.getPageProductIdentity?.() ?? null;
1944
+ if (identity) {
1945
+ this._pageUnresolvable = false;
1946
+ this.productId = identity.productHandle;
1947
+ if (!this.productName && identity.productTitle) this.productName = identity.productTitle;
1948
+ return;
1949
+ }
1950
+ if (!this._pageUnresolvable) {
1951
+ this._pageUnresolvable = true;
1952
+ this.productId = "";
1953
+ const url = typeof window !== "undefined" ? window.location.href : "";
1954
+ console.warn(
1955
+ `[syntro-pdp] no product-id attribute and the page URL did not resolve to a product \u2014 rendering nothing (fail-closed). url=${url}`
1956
+ );
1957
+ }
1958
+ }
1959
+ /**
1960
+ * Subscribe to the runtime's NavigationMonitor so SPA/PJAX product-page
1961
+ * changes re-resolve identity and re-fire (WI-6b). Self-identified elements
1962
+ * only — an attr-driven element's identity is pinned by its attr. Skips
1963
+ * gracefully when the runtime lacks `navigation` (bare test mounts).
1964
+ */
1965
+ _wireNavigation() {
1966
+ if (!this._selfIdentify || this._navUnsub) return;
1967
+ const nav = this.runtimeRef?.navigation;
1968
+ if (!nav || typeof nav.subscribe !== "function") return;
1969
+ this._navUnsub = nav.subscribe((url) => this._onPageNavigated(url));
1970
+ }
1971
+ /** Re-resolve on navigation; on a product change reset the decision machine
1972
+ * through its sanctioned disconnect/reconnect path and re-fire. */
1973
+ _onPageNavigated(navUrl) {
1974
+ if (!this._selfIdentify || !this.isConnected || !this.runtimeRef) return;
1975
+ const identity = this.runtimeRef.getPageProductIdentity?.() ?? null;
1976
+ const next = identity?.productHandle ?? "";
1977
+ if (next === this.productId) return;
1978
+ this._dispatchDecision({ type: "disconnected" });
1979
+ if (identity) {
1980
+ this._pageUnresolvable = false;
1981
+ this.productId = identity.productHandle;
1982
+ if (identity.productTitle) this.productName = identity.productTitle;
1983
+ } else {
1984
+ this.productId = "";
1985
+ if (!this._pageUnresolvable) {
1986
+ this._pageUnresolvable = true;
1987
+ console.warn(
1988
+ `[syntro-pdp] navigation left the product surface \u2014 rendering nothing (fail-closed). url=${navUrl}`
1989
+ );
1990
+ }
1991
+ }
1992
+ this._syncDecisionRuntime(true);
1993
+ }
1883
1994
  _syncDecisionRuntime(reconnecting) {
1995
+ this._resolvePageProduct();
1996
+ this._wireNavigation();
1884
1997
  let appActive = false;
1885
1998
  try {
1886
1999
  appActive = Boolean(this.runtimeRef?.apps?.has?.("adaptive-product"));
@@ -1926,7 +2039,7 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement5 {
1926
2039
  _warnDecision(code, props) {
1927
2040
  if (code === "missing-config") {
1928
2041
  console.warn(
1929
- `[syntro-pdp] no PDP presets for product "${this.productId}" (or the scored-template primitive is unavailable) \u2014 skipping personalization. Configure __SYNTRO_PDP_PRESETS__ to enable the config-first PDP.`
2042
+ `[syntro-pdp] no PDP presets for product "${this.productId}" (or the scored-template primitive is unavailable) \u2014 skipping personalization. Deliver presets via the SDK config \`pdpPresetsUrl\` (or the legacy __SYNTRO_PDP_PRESETS__ boot global) to enable the config-first PDP.`
1930
2043
  );
1931
2044
  return;
1932
2045
  }
@@ -2430,6 +2543,10 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement5 {
2430
2543
  `;
2431
2544
  }
2432
2545
  render() {
2546
+ if (this._pageUnresolvable) return html5``;
2547
+ if (this._presetsSettled && this._decisionState.stream.status === "idle" && this._decisionState.plan.status === "empty" && !_readPdpPresets()?.[this.productId]) {
2548
+ return html5``;
2549
+ }
2433
2550
  const skeletonStyles = _SyntroPdpLit._skeletonStyles;
2434
2551
  const view = selectPdpView(this._decisionState);
2435
2552
  if (view.layout === "skeleton") {
@@ -2502,7 +2619,9 @@ _SyntroPdpLit.properties = {
2502
2619
  productName: { type: String, attribute: "product-name" },
2503
2620
  productBlurb: { type: String, attribute: "product-blurb" },
2504
2621
  runtimeRef: { attribute: false },
2505
- _decisionState: { state: true }
2622
+ _decisionState: { state: true },
2623
+ _pageUnresolvable: { state: true },
2624
+ _presetsSettled: { state: true }
2506
2625
  };
2507
2626
  /**
2508
2627
  * Self-contained skeleton (shimmer) CSS. Rendered inside the component
@@ -2598,8 +2717,7 @@ function readSynOS() {
2598
2717
  return window.SynOS;
2599
2718
  }
2600
2719
  function readPresets() {
2601
- if (typeof window === "undefined") return void 0;
2602
- return window.__SYNTRO_PDP_PRESETS__;
2720
+ return getPdpPresets();
2603
2721
  }
2604
2722
  function readArchetypeDescriptions() {
2605
2723
  if (typeof window === "undefined") return {};
@@ -2705,6 +2823,7 @@ function summaryFor(pair) {
2705
2823
  return pair.decision !== UNKNOWN ? `Tailored for: ${pair.archetype} \xB7 ${pair.decision}` : `Tailored for: ${pair.archetype}`;
2706
2824
  }
2707
2825
  async function computeDecision(productId) {
2826
+ if (pdpPresetsFetchStarted()) await pdpPresetsReady();
2708
2827
  const productPresets = readPresets()?.[productId];
2709
2828
  const score = readSynOS()?.score;
2710
2829
  if (!productPresets || typeof score !== "function") {
@@ -10960,4 +11079,4 @@ export {
10960
11079
  * SPDX-License-Identifier: BSD-3-Clause
10961
11080
  *)
10962
11081
  */
10963
- //# sourceMappingURL=chunk-VY23O32Z.js.map
11082
+ //# sourceMappingURL=chunk-QC5B4ITM.js.map