@syntrologie/adapt-product 2.8.0-canary.430 → 2.8.0-canary.432

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-5CIX67HG.js";
4
+ } from "./chunk-F6VOPJYW.js";
5
5
  import "./chunk-F3ZOS4JD.js";
6
6
 
7
7
  // src/cdn.ts
@@ -3041,9 +3041,66 @@ if (!customElements.get("syntro-product-reco-carousel"))
3041
3041
  // src/widgets/SyntroPdpLit.ts
3042
3042
  import { html as html11, LitElement as LitElement11, render } from "lit";
3043
3043
 
3044
+ // src/scoring/pool-selection.ts
3045
+ var UNKNOWN_DECISION = "unknown";
3046
+ var DISPLAY_MAX = {
3047
+ qa: 5,
3048
+ "trending-news": 5,
3049
+ "peer-feed": 4
3050
+ };
3051
+ function tagsOf(item) {
3052
+ return Array.isArray(item.archetypes) ? item.archetypes : [];
3053
+ }
3054
+ function selectPoolItems(items, decision, max) {
3055
+ if (items.length <= max) return [...items];
3056
+ const sharp = decision && decision !== UNKNOWN_DECISION;
3057
+ if (sharp) {
3058
+ const matched = items.filter((i2) => tagsOf(i2).includes(decision));
3059
+ const general = items.filter((i2) => tagsOf(i2).length === 0);
3060
+ const rest = items.filter((i2) => !matched.includes(i2) && !general.includes(i2));
3061
+ return [...matched, ...general, ...rest].slice(0, max);
3062
+ }
3063
+ const picked = [];
3064
+ const covered = /* @__PURE__ */ new Set();
3065
+ for (const item of items) {
3066
+ const tags = tagsOf(item);
3067
+ if (tags.length === 0) continue;
3068
+ if (tags.some((t2) => covered.has(t2))) continue;
3069
+ picked.push(item);
3070
+ for (const t2 of tags) covered.add(t2);
3071
+ if (picked.length >= max) return picked.slice(0, max);
3072
+ }
3073
+ for (const item of items) {
3074
+ if (picked.length >= max) break;
3075
+ if (!picked.includes(item) && tagsOf(item).length === 0) picked.push(item);
3076
+ }
3077
+ for (const item of items) {
3078
+ if (picked.length >= max) break;
3079
+ if (!picked.includes(item)) picked.push(item);
3080
+ }
3081
+ return picked.slice(0, max);
3082
+ }
3083
+ function selectPresetData(kind, data, decision) {
3084
+ const max = DISPLAY_MAX[kind];
3085
+ if (!max || typeof data !== "object" || data === null) return data;
3086
+ const record = data;
3087
+ const listKey = kind === "peer-feed" ? "peers" : "items";
3088
+ const list = record[listKey];
3089
+ if (!Array.isArray(list)) return data;
3090
+ return { ...record, [listKey]: selectPoolItems(list, decision, max) };
3091
+ }
3092
+
3044
3093
  // src/scoring/scored-template.ts
3045
3094
  async function resolveScoredTemplate(opts) {
3046
- const value = await opts.score(opts.scoreSpec);
3095
+ let value;
3096
+ let decisionValue = "unknown";
3097
+ if (opts.decisionSpec && opts.scoreMulti) {
3098
+ const scored = await opts.scoreMulti([opts.scoreSpec, opts.decisionSpec]);
3099
+ value = scored[0] ?? "unknown";
3100
+ decisionValue = scored[1] ?? "unknown";
3101
+ } else {
3102
+ value = await opts.score(opts.scoreSpec);
3103
+ }
3047
3104
  const variant = opts.presets[value] ?? opts.presets[opts.fallbackValue ?? "unknown"] ?? {};
3048
3105
  const setSlots = opts.slotIds.filter((s4) => s4 in variant);
3049
3106
  const unsetSlots = opts.slotIds.filter((s4) => !(s4 in variant));
@@ -3052,12 +3109,13 @@ async function resolveScoredTemplate(opts) {
3052
3109
  opts.emitPlan(opts.buildPlan(pageOrder.map((slot) => ({ slot, preset: variant[slot] }))));
3053
3110
  }
3054
3111
  for (const slot of setSlots) {
3055
- await opts.emitMount(opts.buildMount(slot, variant[slot]));
3112
+ const preset = opts.transformPreset ? opts.transformPreset(variant[slot], decisionValue) : variant[slot];
3113
+ await opts.emitMount(opts.buildMount(slot, preset));
3056
3114
  }
3057
3115
  if (unsetSlots.length > 0 && opts.composeUnset) {
3058
- await opts.composeUnset(unsetSlots, value);
3116
+ await opts.composeUnset(unsetSlots, value, decisionValue);
3059
3117
  }
3060
- return { value, setSlots, unsetSlots };
3118
+ return { value, decisionValue, setSlots, unsetSlots };
3061
3119
  }
3062
3120
 
3063
3121
  // src/widgets/PdpSectionHeaderLit.ts
@@ -3628,16 +3686,28 @@ function _readPdpArchetypes() {
3628
3686
  if (typeof window === "undefined") return void 0;
3629
3687
  return window.__SYNTRO_PDP_ARCHETYPES__;
3630
3688
  }
3689
+ function _readPdpDecisionArchetypes() {
3690
+ if (typeof window === "undefined") return void 0;
3691
+ return window.__SYNTRO_PDP_DECISION_ARCHETYPES__;
3692
+ }
3631
3693
  async function composeModule(authedFetch, args) {
3632
3694
  try {
3633
3695
  const requestBody = {
3634
3696
  product_name: args.productName,
3697
+ // Scopes the peer-feed's review-corpus fetch to this product.
3698
+ product_id: args.productId,
3635
3699
  // `kind` flows from runtime slot-id parsing (`pdp-slot-<kind>`), so it's a
3636
3700
  // plain string here; the backend validates it against the PdpModuleKind
3637
3701
  // enum (422 on an unknown kind). The other fields ARE contract-checked.
3638
3702
  kind: args.kind,
3639
3703
  variant: args.variant,
3640
3704
  archetype: { slug: args.archetype.slug, description: args.archetype.description },
3705
+ // Axis A — what's blocking the purchase. Null (unknown / undeclared)
3706
+ // degrades server-side to facet-only intent.
3707
+ decision_archetype: args.decisionArchetype,
3708
+ // Live per-visitor generation is always focused; the preset generator is
3709
+ // the only 'pool' caller.
3710
+ coverage: "focused",
3641
3711
  behavior_summary: args.behaviorSummary,
3642
3712
  // geo/attribution/device — the regional module needs the visitor's region.
3643
3713
  signals: args.signals
@@ -3720,7 +3790,17 @@ function _sessionMetric(signals, key) {
3720
3790
  return typeof v === "number" && Number.isFinite(v) ? v : null;
3721
3791
  }
3722
3792
  function buildExplain(opts) {
3723
- const { archetype, description, summary, signals, latencyMs } = opts;
3793
+ const {
3794
+ archetype,
3795
+ description,
3796
+ decisionArchetype,
3797
+ decisionDescription,
3798
+ summary,
3799
+ signals,
3800
+ latencyMs
3801
+ } = opts;
3802
+ const blockingQuestion = /Blocked on:\s*([\s\S]*?)\s*Resolved by:/.exec(decisionDescription)?.[1] ?? decisionDescription;
3803
+ const decisionKnown = !!decisionArchetype && decisionArchetype !== "unknown";
3724
3804
  const signalsCount = _countSignals(signals);
3725
3805
  const dims = _signalDimensions(signals);
3726
3806
  const fingerprint = `0x${_fingerprint(signals)}`;
@@ -3762,7 +3842,7 @@ function buildExplain(opts) {
3762
3842
  {
3763
3843
  value: "2",
3764
3844
  label: "AI models run live",
3765
- hint: "An archetype classifier and a behavior-summary model, both evaluated in real time on your signals."
3845
+ hint: "A two-axis archetype classifier (what you do + what is blocking your decision) and a behavior-summary model, evaluated in real time on your signals."
3766
3846
  },
3767
3847
  {
3768
3848
  value: latency,
@@ -3789,6 +3869,8 @@ function buildExplain(opts) {
3789
3869
  return {
3790
3870
  archetypeLabel: _titleCase(archetype),
3791
3871
  archetypeDesc: description,
3872
+ decisionLabel: decisionKnown ? _titleCase(decisionArchetype) : null,
3873
+ decisionDesc: decisionKnown ? blockingQuestion : null,
3792
3874
  summary,
3793
3875
  metrics: hero,
3794
3876
  dossier
@@ -4248,7 +4330,7 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement11 {
4248
4330
  const productPresets = _readPdpPresets()?.[this.productId];
4249
4331
  const synos = _readSynOS();
4250
4332
  if (productPresets && typeof synos?.score === "function") {
4251
- void this._fireFromConfig(productPresets, synos.score, synos.authedFetch);
4333
+ void this._fireFromConfig(productPresets, synos.score, synos.authedFetch, synos.scoreMulti);
4252
4334
  return;
4253
4335
  }
4254
4336
  console.warn(
@@ -4298,12 +4380,14 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement11 {
4298
4380
  }
4299
4381
  }
4300
4382
  /** Render from config: score the archetype → mount the preset set-values. */
4301
- async _fireFromConfig(productPresets, score, authedFetch) {
4383
+ async _fireFromConfig(productPresets, score, authedFetch, scoreMulti) {
4302
4384
  const startedAt = performance.now();
4303
4385
  const gen = ++this._fireGeneration;
4304
4386
  this._streamState = "streaming";
4305
4387
  const archetypeValues = Object.keys(productPresets);
4306
4388
  const archetypeDescriptions = _readPdpArchetypes() ?? {};
4389
+ const decisionDescriptions = _readPdpDecisionArchetypes() ?? {};
4390
+ const decisionValues = Object.keys(decisionDescriptions);
4307
4391
  const signals = { ...gatherVisitorSignals(), chat_excerpt: readChatExcerpt() };
4308
4392
  const summaryPromise = authedFetch ? score({ model_id: "summary", name: "behavior_summary", type: "string", signals }).catch(
4309
4393
  () => "unknown"
@@ -4327,6 +4411,23 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement11 {
4327
4411
  ),
4328
4412
  signals
4329
4413
  },
4414
+ // Axis A: scored together with the facet in ONE model call when the
4415
+ // host declares decision archetypes and the runtime exposes scoreMulti.
4416
+ decisionSpec: decisionValues.length > 0 && scoreMulti ? {
4417
+ model_id: "llm",
4418
+ name: "decision_archetype",
4419
+ type: "enum",
4420
+ values: decisionValues,
4421
+ value_descriptions: decisionDescriptions,
4422
+ signals
4423
+ } : void 0,
4424
+ scoreMulti,
4425
+ // Pool selection: the cached preset carries more tagged items than the
4426
+ // widget shows; the decision archetype picks which ones surface.
4427
+ transformPreset: (preset, decisionValue) => ({
4428
+ ...preset,
4429
+ data: selectPresetData(preset.kind, preset.data, decisionValue)
4430
+ }),
4330
4431
  presets: productPresets,
4331
4432
  slotIds: SCORED_PDP_SLOTS,
4332
4433
  orderSlots: _orderSlotsForPage,
@@ -4369,8 +4470,9 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement11 {
4369
4470
  // Live-generate the uncached slots (chart/regional) via /api/pdp/module.
4370
4471
  // The angle = archetype description + behavior summary (no planner). The
4371
4472
  // boxes are already reserved by the plan above, so these fill in place.
4372
- composeUnset: authedFetch ? async (unsetSlots, archetypeValue) => {
4473
+ composeUnset: authedFetch ? async (unsetSlots, archetypeValue, decisionValue) => {
4373
4474
  const description = archetypeDescriptions[archetypeValue] ?? "";
4475
+ const decisionArchetype = decisionValue && decisionValue !== "unknown" ? { slug: decisionValue, description: decisionDescriptions[decisionValue] ?? "" } : null;
4374
4476
  const rawSummary2 = await summaryPromise;
4375
4477
  const behaviorSummary = rawSummary2 === "unknown" ? "" : rawSummary2;
4376
4478
  await Promise.all(
@@ -4378,9 +4480,11 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement11 {
4378
4480
  const kind = slot.replace("pdp-slot-", "");
4379
4481
  const payload = await composeModule(authedFetch, {
4380
4482
  productName: this.productName,
4483
+ productId: this.productId || null,
4381
4484
  kind,
4382
4485
  variant: null,
4383
4486
  archetype: { slug: archetypeValue, description },
4487
+ decisionArchetype,
4384
4488
  behaviorSummary,
4385
4489
  signals
4386
4490
  });
@@ -4400,6 +4504,8 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement11 {
4400
4504
  this._explain = buildExplain({
4401
4505
  archetype: result.value,
4402
4506
  description: archetypeDescriptions[result.value] ?? "",
4507
+ decisionArchetype: result.decisionValue,
4508
+ decisionDescription: decisionDescriptions[result.decisionValue] ?? "",
4403
4509
  summary: rawSummary === "unknown" ? "" : rawSummary,
4404
4510
  signals,
4405
4511
  latencyMs: performance.now() - startedAt
@@ -4411,10 +4517,11 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement11 {
4411
4517
  }).catch(() => {
4412
4518
  });
4413
4519
  const archLabel = _titleCase(result.value);
4520
+ const decisionLabel = result.decisionValue && result.decisionValue !== "unknown" ? _titleCase(result.decisionValue) : null;
4414
4521
  for (const slot of result.setSlots) {
4415
4522
  this._setSlotReasoning(slot, {
4416
4523
  source: "cached",
4417
- text: `Chosen from the content library \u2014 matched to your \u201C${archLabel}\u201D archetype.`
4524
+ text: decisionLabel ? `Chosen from the content library \u2014 matched to your \u201C${archLabel}\u201D archetype, showing the items that answer your \u201C${decisionLabel}\u201D question first.` : `Chosen from the content library \u2014 matched to your \u201C${archLabel}\u201D archetype.`
4418
4525
  });
4419
4526
  }
4420
4527
  this._track("syntro_pdp_done", {
@@ -4527,6 +4634,9 @@ var _SyntroPdpLit = class _SyntroPdpLit extends LitElement11 {
4527
4634
  <div data-syntro-pdp-why-archetype>
4528
4635
  Detected archetype: <strong>${e2.archetypeLabel}</strong>${e2.archetypeDesc ? html11` — ${e2.archetypeDesc}` : ""}
4529
4636
  </div>
4637
+ ${e2.decisionLabel ? html11`<div data-syntro-pdp-why-archetype>
4638
+ What's blocking the decision: <strong>${e2.decisionLabel}</strong>${e2.decisionDesc ? html11` — “${e2.decisionDesc}”` : ""}
4639
+ </div>` : ""}
4530
4640
  ${e2.summary ? html11`<div data-syntro-pdp-why-summary>"${e2.summary}"</div>` : ""}
4531
4641
  <div data-syntro-pdp-why-metrics>
4532
4642
  ${e2.metrics.map(
@@ -9347,4 +9457,4 @@ export {
9347
9457
  * SPDX-License-Identifier: BSD-3-Clause
9348
9458
  *)
9349
9459
  */
9350
- //# sourceMappingURL=chunk-5CIX67HG.js.map
9460
+ //# sourceMappingURL=chunk-F6VOPJYW.js.map