@wix/web5-core 1.57.2 → 1.59.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 (87) hide show
  1. package/dist/cjs/client/applyThemeOverrides.js +89 -0
  2. package/dist/cjs/client/applyThemeOverrides.js.map +1 -0
  3. package/dist/cjs/client/clientBundleUrl.js +83 -0
  4. package/dist/cjs/client/clientBundleUrl.js.map +1 -0
  5. package/dist/cjs/client/mergeClientConfig.js +59 -0
  6. package/dist/cjs/client/mergeClientConfig.js.map +1 -0
  7. package/dist/cjs/component/componentDefinitions/EntityCollectionSectionDefinition.js +159 -0
  8. package/dist/cjs/component/componentDefinitions/EntityCollectionSectionDefinition.js.map +1 -0
  9. package/dist/cjs/component/componentDefinitions/HeroEntitySectionDefinition.js +5 -2
  10. package/dist/cjs/component/componentDefinitions/HeroEntitySectionDefinition.js.map +1 -1
  11. package/dist/cjs/component/componentDefinitions/HeroSectionDefinition.js +4 -1
  12. package/dist/cjs/component/componentDefinitions/HeroSectionDefinition.js.map +1 -1
  13. package/dist/cjs/component/componentDefinitions/NextStepsSectionDefinition.js +35 -2
  14. package/dist/cjs/component/componentDefinitions/NextStepsSectionDefinition.js.map +1 -1
  15. package/dist/cjs/component/componentDefinitions/TextBlockSectionDefinition.js +27 -1
  16. package/dist/cjs/component/componentDefinitions/TextBlockSectionDefinition.js.map +1 -1
  17. package/dist/cjs/component/componentDefinitions/index.js +14 -1
  18. package/dist/cjs/component/componentDefinitions/index.js.map +1 -1
  19. package/dist/cjs/component/componentParser.js +38 -1
  20. package/dist/cjs/component/componentParser.js.map +1 -1
  21. package/dist/cjs/component/types.js.map +1 -1
  22. package/dist/cjs/components/ui/SearchSection.css +66 -9
  23. package/dist/cjs/components/ui/SearchSection.js +23 -19
  24. package/dist/cjs/components/ui/SearchSection.js.map +1 -1
  25. package/dist/cjs/hostScope.js.map +1 -1
  26. package/dist/cjs/index.js +23 -3
  27. package/dist/cjs/index.js.map +1 -1
  28. package/dist/cjs/styles/prompt-input-tokens.css +41 -34
  29. package/dist/cjs/utils/matchDebug.js +93 -0
  30. package/dist/cjs/utils/matchDebug.js.map +1 -0
  31. package/dist/esm/client/applyThemeOverrides.js +85 -0
  32. package/dist/esm/client/applyThemeOverrides.js.map +1 -0
  33. package/dist/esm/client/clientBundleUrl.js +75 -0
  34. package/dist/esm/client/clientBundleUrl.js.map +1 -0
  35. package/dist/esm/client/mergeClientConfig.js +55 -0
  36. package/dist/esm/client/mergeClientConfig.js.map +1 -0
  37. package/dist/esm/component/componentDefinitions/EntityCollectionSectionDefinition.js +154 -0
  38. package/dist/esm/component/componentDefinitions/EntityCollectionSectionDefinition.js.map +1 -0
  39. package/dist/esm/component/componentDefinitions/HeroEntitySectionDefinition.js +6 -3
  40. package/dist/esm/component/componentDefinitions/HeroEntitySectionDefinition.js.map +1 -1
  41. package/dist/esm/component/componentDefinitions/HeroSectionDefinition.js +5 -2
  42. package/dist/esm/component/componentDefinitions/HeroSectionDefinition.js.map +1 -1
  43. package/dist/esm/component/componentDefinitions/NextStepsSectionDefinition.js +36 -3
  44. package/dist/esm/component/componentDefinitions/NextStepsSectionDefinition.js.map +1 -1
  45. package/dist/esm/component/componentDefinitions/TextBlockSectionDefinition.js +27 -1
  46. package/dist/esm/component/componentDefinitions/TextBlockSectionDefinition.js.map +1 -1
  47. package/dist/esm/component/componentDefinitions/index.js +14 -1
  48. package/dist/esm/component/componentDefinitions/index.js.map +1 -1
  49. package/dist/esm/component/componentParser.js +38 -1
  50. package/dist/esm/component/componentParser.js.map +1 -1
  51. package/dist/esm/component/types.js.map +1 -1
  52. package/dist/esm/components/ui/SearchSection.css +66 -9
  53. package/dist/esm/components/ui/SearchSection.js +5 -1
  54. package/dist/esm/components/ui/SearchSection.js.map +1 -1
  55. package/dist/esm/hostScope.js.map +1 -1
  56. package/dist/esm/index.js +9 -1
  57. package/dist/esm/index.js.map +1 -1
  58. package/dist/esm/styles/prompt-input-tokens.css +41 -34
  59. package/dist/esm/utils/matchDebug.js +85 -0
  60. package/dist/esm/utils/matchDebug.js.map +1 -0
  61. package/dist/types/client/applyThemeOverrides.d.ts +19 -0
  62. package/dist/types/client/applyThemeOverrides.d.ts.map +1 -0
  63. package/dist/types/client/clientBundleUrl.d.ts +39 -0
  64. package/dist/types/client/clientBundleUrl.d.ts.map +1 -0
  65. package/dist/types/client/mergeClientConfig.d.ts +28 -0
  66. package/dist/types/client/mergeClientConfig.d.ts.map +1 -0
  67. package/dist/types/component/componentDefinitions/EntityCollectionSectionDefinition.d.ts +51 -0
  68. package/dist/types/component/componentDefinitions/EntityCollectionSectionDefinition.d.ts.map +1 -0
  69. package/dist/types/component/componentDefinitions/HeroEntitySectionDefinition.d.ts.map +1 -1
  70. package/dist/types/component/componentDefinitions/HeroSectionDefinition.d.ts.map +1 -1
  71. package/dist/types/component/componentDefinitions/NextStepsSectionDefinition.d.ts.map +1 -1
  72. package/dist/types/component/componentDefinitions/TextBlockSectionDefinition.d.ts.map +1 -1
  73. package/dist/types/component/componentDefinitions/index.d.ts +1 -0
  74. package/dist/types/component/componentDefinitions/index.d.ts.map +1 -1
  75. package/dist/types/component/componentParser.d.ts.map +1 -1
  76. package/dist/types/component/types.d.ts +7 -0
  77. package/dist/types/component/types.d.ts.map +1 -1
  78. package/dist/types/components/ui/SearchSection.d.ts.map +1 -1
  79. package/dist/types/hostScope.d.ts +11 -0
  80. package/dist/types/hostScope.d.ts.map +1 -1
  81. package/dist/types/index.d.ts +5 -1
  82. package/dist/types/index.d.ts.map +1 -1
  83. package/dist/types/utils/matchDebug.d.ts +38 -0
  84. package/dist/types/utils/matchDebug.d.ts.map +1 -0
  85. package/package.json +2 -2
  86. package/src/components/ui/SearchSection.css +66 -9
  87. package/src/styles/prompt-input-tokens.css +41 -34
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.setMatchDebug = exports.resetMatchDebugCache = exports.logMatchDebug = exports.isMatchDebugEnabled = exports.MATCH_DEBUG_QUERY_PARAM = exports.MATCH_DEBUG_KEY = void 0;
5
+ /**
6
+ * Section-match tracing (debug-only).
7
+ *
8
+ * Turns the otherwise silent matching loop in `tryParseComponent` into a
9
+ * console trace: which definitions the active registry holds and in what
10
+ * order, which patterns were tried against a block, why each one failed, and
11
+ * when a matching definition then returned `null` from `parse()` and let the
12
+ * block fall through to the next candidate.
13
+ *
14
+ * Answers the two questions a "my section isn't rendering" report always
15
+ * splits into — *is my definition even registered?* (a missing entry in the
16
+ * registry order line usually means a stale bundle or a `web5-core` version
17
+ * skew; see the warning in `ComponentRegistry.register`) and *did it lose the
18
+ * match, or lose the parse?*
19
+ *
20
+ * Off by default. Enable with `?web5DebugMatch=1` (one-shot, wins over
21
+ * storage) or `localStorage["web5_debug_match"] = "1"` (sticky). Resolved once
22
+ * per page load — the tracing is on the hot parse path, so it must not read
23
+ * storage per block.
24
+ */
25
+
26
+ const MATCH_DEBUG_KEY = exports.MATCH_DEBUG_KEY = 'web5_debug_match';
27
+
28
+ /** Query param that forces match tracing on, overriding localStorage. */
29
+ const MATCH_DEBUG_QUERY_PARAM = exports.MATCH_DEBUG_QUERY_PARAM = 'web5DebugMatch';
30
+ const LOG_PREFIX = '[web5:match]';
31
+ const isTruthy = value => value === '1' || value === 'true';
32
+ let cached = null;
33
+
34
+ /**
35
+ * Whether match tracing is enabled. Memoized: the parse loop calls this for
36
+ * every block of every turn.
37
+ */
38
+ const isMatchDebugEnabled = () => {
39
+ if (cached !== null) {
40
+ return cached;
41
+ }
42
+ cached = false;
43
+ try {
44
+ if (isTruthy(new URLSearchParams(window.location.search).get(MATCH_DEBUG_QUERY_PARAM))) {
45
+ cached = true;
46
+ return cached;
47
+ }
48
+ } catch {
49
+ // window / URLSearchParams unavailable (SSR, non-DOM environments).
50
+ }
51
+ try {
52
+ cached = isTruthy(localStorage.getItem(MATCH_DEBUG_KEY));
53
+ } catch {
54
+ // localStorage can throw in privacy mode / non-DOM environments.
55
+ }
56
+ return cached;
57
+ };
58
+
59
+ /** Persist the toggle and apply it to the current page without a reload. */
60
+ exports.isMatchDebugEnabled = isMatchDebugEnabled;
61
+ const setMatchDebug = enabled => {
62
+ cached = enabled;
63
+ try {
64
+ if (enabled) {
65
+ localStorage.setItem(MATCH_DEBUG_KEY, '1');
66
+ } else {
67
+ localStorage.removeItem(MATCH_DEBUG_KEY);
68
+ }
69
+ } catch {
70
+ // Ignore — the toggle simply won't persist across reloads.
71
+ }
72
+ };
73
+
74
+ /** Drop the memoized value (tests, and after an external storage change). */
75
+ exports.setMatchDebug = setMatchDebug;
76
+ const resetMatchDebugCache = () => {
77
+ cached = null;
78
+ };
79
+
80
+ /**
81
+ * Log one trace line. Callers must gate on {@link isMatchDebugEnabled} before
82
+ * building expensive messages; this only guards the write.
83
+ */
84
+ exports.resetMatchDebugCache = resetMatchDebugCache;
85
+ const logMatchDebug = message => {
86
+ if (!isMatchDebugEnabled()) {
87
+ return;
88
+ }
89
+ // eslint-disable-next-line no-console
90
+ console.log(`${LOG_PREFIX} ${message}`);
91
+ };
92
+ exports.logMatchDebug = logMatchDebug;
93
+ //# sourceMappingURL=matchDebug.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["MATCH_DEBUG_KEY","exports","MATCH_DEBUG_QUERY_PARAM","LOG_PREFIX","isTruthy","value","cached","isMatchDebugEnabled","URLSearchParams","window","location","search","get","localStorage","getItem","setMatchDebug","enabled","setItem","removeItem","resetMatchDebugCache","logMatchDebug","message","console","log"],"sources":["../../../src/utils/matchDebug.ts"],"sourcesContent":["/**\n * Section-match tracing (debug-only).\n *\n * Turns the otherwise silent matching loop in `tryParseComponent` into a\n * console trace: which definitions the active registry holds and in what\n * order, which patterns were tried against a block, why each one failed, and\n * when a matching definition then returned `null` from `parse()` and let the\n * block fall through to the next candidate.\n *\n * Answers the two questions a \"my section isn't rendering\" report always\n * splits into — *is my definition even registered?* (a missing entry in the\n * registry order line usually means a stale bundle or a `web5-core` version\n * skew; see the warning in `ComponentRegistry.register`) and *did it lose the\n * match, or lose the parse?*\n *\n * Off by default. Enable with `?web5DebugMatch=1` (one-shot, wins over\n * storage) or `localStorage[\"web5_debug_match\"] = \"1\"` (sticky). Resolved once\n * per page load — the tracing is on the hot parse path, so it must not read\n * storage per block.\n */\n\nexport const MATCH_DEBUG_KEY = 'web5_debug_match';\n\n/** Query param that forces match tracing on, overriding localStorage. */\nexport const MATCH_DEBUG_QUERY_PARAM = 'web5DebugMatch';\n\nconst LOG_PREFIX = '[web5:match]';\n\nconst isTruthy = (value: unknown): boolean => value === '1' || value === 'true';\n\nlet cached: boolean | null = null;\n\n/**\n * Whether match tracing is enabled. Memoized: the parse loop calls this for\n * every block of every turn.\n */\nexport const isMatchDebugEnabled = (): boolean => {\n if (cached !== null) {\n return cached;\n }\n cached = false;\n try {\n if (\n isTruthy(\n new URLSearchParams(window.location.search).get(\n MATCH_DEBUG_QUERY_PARAM,\n ),\n )\n ) {\n cached = true;\n return cached;\n }\n } catch {\n // window / URLSearchParams unavailable (SSR, non-DOM environments).\n }\n try {\n cached = isTruthy(localStorage.getItem(MATCH_DEBUG_KEY));\n } catch {\n // localStorage can throw in privacy mode / non-DOM environments.\n }\n return cached;\n};\n\n/** Persist the toggle and apply it to the current page without a reload. */\nexport const setMatchDebug = (enabled: boolean): void => {\n cached = enabled;\n try {\n if (enabled) {\n localStorage.setItem(MATCH_DEBUG_KEY, '1');\n } else {\n localStorage.removeItem(MATCH_DEBUG_KEY);\n }\n } catch {\n // Ignore — the toggle simply won't persist across reloads.\n }\n};\n\n/** Drop the memoized value (tests, and after an external storage change). */\nexport const resetMatchDebugCache = (): void => {\n cached = null;\n};\n\n/**\n * Log one trace line. Callers must gate on {@link isMatchDebugEnabled} before\n * building expensive messages; this only guards the write.\n */\nexport const logMatchDebug = (message: string): void => {\n if (!isMatchDebugEnabled()) {\n return;\n }\n // eslint-disable-next-line no-console\n console.log(`${LOG_PREFIX} ${message}`);\n};\n"],"mappings":";;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEO,MAAMA,eAAe,GAAAC,OAAA,CAAAD,eAAA,GAAG,kBAAkB;;AAEjD;AACO,MAAME,uBAAuB,GAAAD,OAAA,CAAAC,uBAAA,GAAG,gBAAgB;AAEvD,MAAMC,UAAU,GAAG,cAAc;AAEjC,MAAMC,QAAQ,GAAIC,KAAc,IAAcA,KAAK,KAAK,GAAG,IAAIA,KAAK,KAAK,MAAM;AAE/E,IAAIC,MAAsB,GAAG,IAAI;;AAEjC;AACA;AACA;AACA;AACO,MAAMC,mBAAmB,GAAGA,CAAA,KAAe;EAChD,IAAID,MAAM,KAAK,IAAI,EAAE;IACnB,OAAOA,MAAM;EACf;EACAA,MAAM,GAAG,KAAK;EACd,IAAI;IACF,IACEF,QAAQ,CACN,IAAII,eAAe,CAACC,MAAM,CAACC,QAAQ,CAACC,MAAM,CAAC,CAACC,GAAG,CAC7CV,uBACF,CACF,CAAC,EACD;MACAI,MAAM,GAAG,IAAI;MACb,OAAOA,MAAM;IACf;EACF,CAAC,CAAC,MAAM;IACN;EAAA;EAEF,IAAI;IACFA,MAAM,GAAGF,QAAQ,CAACS,YAAY,CAACC,OAAO,CAACd,eAAe,CAAC,CAAC;EAC1D,CAAC,CAAC,MAAM;IACN;EAAA;EAEF,OAAOM,MAAM;AACf,CAAC;;AAED;AAAAL,OAAA,CAAAM,mBAAA,GAAAA,mBAAA;AACO,MAAMQ,aAAa,GAAIC,OAAgB,IAAW;EACvDV,MAAM,GAAGU,OAAO;EAChB,IAAI;IACF,IAAIA,OAAO,EAAE;MACXH,YAAY,CAACI,OAAO,CAACjB,eAAe,EAAE,GAAG,CAAC;IAC5C,CAAC,MAAM;MACLa,YAAY,CAACK,UAAU,CAAClB,eAAe,CAAC;IAC1C;EACF,CAAC,CAAC,MAAM;IACN;EAAA;AAEJ,CAAC;;AAED;AAAAC,OAAA,CAAAc,aAAA,GAAAA,aAAA;AACO,MAAMI,oBAAoB,GAAGA,CAAA,KAAY;EAC9Cb,MAAM,GAAG,IAAI;AACf,CAAC;;AAED;AACA;AACA;AACA;AAHAL,OAAA,CAAAkB,oBAAA,GAAAA,oBAAA;AAIO,MAAMC,aAAa,GAAIC,OAAe,IAAW;EACtD,IAAI,CAACd,mBAAmB,CAAC,CAAC,EAAE;IAC1B;EACF;EACA;EACAe,OAAO,CAACC,GAAG,CAAC,GAAGpB,UAAU,IAAIkB,OAAO,EAAE,CAAC;AACzC,CAAC;AAACpB,OAAA,CAAAmB,aAAA,GAAAA,aAAA","ignoreList":[]}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Runtime theme token injection (DL #131).
3
+ *
4
+ * `Configuration.themeOverrides` carries per-store CSS custom-property
5
+ * overrides (`--primary`, `--radius`, `--heading`, ...) computed from the
6
+ * store's own theme (e.g. the Shopify `settings_data.json` extractor). This
7
+ * applies them to the page so they win over the bundle's baked `theme.css`:
8
+ *
9
+ * - **Scoped, not `:root`**: the rule targets `WEB5_SCOPE`, the same
10
+ * `:is(#root, .w5-root, [data-web5-placement], .debug-view)` selector every
11
+ * compiled bundle stylesheet uses — host-page styling is never touched, and
12
+ * equal specificity + later document order makes the override win. Callers
13
+ * must therefore apply AFTER the client bundle's CSS is in the document.
14
+ * - **Open token set**: any sanely-shaped custom property is applied, so a
15
+ * template can grow new tokens without an FE or BE release. The mandatory
16
+ * `--` prefix means an override can only define custom properties — it can
17
+ * never set a real CSS property inside the scope. The server enforces the
18
+ * same shape (plus value sanitation) at write time.
19
+ * - **Values are inert**: entries are written with
20
+ * `CSSStyleDeclaration.setProperty`, never string-concatenated into CSS, so
21
+ * a hostile value cannot terminate the declaration or open a new rule.
22
+ * - **Idempotent**: one marker element per document, replaced wholesale on
23
+ * re-apply; empty/absent input removes it.
24
+ */
25
+ import { WEB5_SCOPE } from '../hostScope.js';
26
+
27
+ /** A themeOverrides key: always a CSS custom property. */
28
+
29
+ /**
30
+ * Writer-side shape for a theme override map. The catalog below documents
31
+ * the tokens the seed consumes today; the type stays open so new tokens
32
+ * flow end-to-end with no release anywhere but the seed.
33
+ */
34
+
35
+ /**
36
+ * The known token catalog — the CSS custom properties the seed's Tailwind
37
+ * bridge consumes today. GUIDANCE, NOT A GATE: writers (the Shopify theme
38
+ * extractor, admin tooling) should stick to these to produce visible effect,
39
+ * but `applyThemeOverrides` deliberately applies any well-formed custom
40
+ * property so the vocabulary can grow from the seed alone (matching the
41
+ * open-set validation on the server).
42
+ */
43
+ export const THEME_OVERRIDE_TOKENS = new Set(['--background', '--foreground', '--card', '--card-foreground', '--popover', '--popover-foreground', '--primary', '--primary-foreground', '--secondary', '--secondary-foreground', '--muted', '--muted-foreground', '--accent', '--accent-foreground', '--border', '--input', '--ring', '--radius', '--font-sans', '--font-display', '--heading']);
44
+
45
+ /** Mirrors the server's Consts.ThemeOverrideKeyPattern. */
46
+ const KEY_PATTERN = /^--[a-z][a-z0-9-]{0,62}$/;
47
+ const MARKER_ATTR = 'data-web5-theme-overrides';
48
+ export function applyThemeOverrides(overrides) {
49
+ if (typeof document === 'undefined') return;
50
+ const existing = document.head.querySelector(`style[${MARKER_ATTR}]`);
51
+ const entries = Object.entries(overrides ?? {}).filter(_ref => {
52
+ let [key] = _ref;
53
+ const wellFormed = KEY_PATTERN.test(key);
54
+ if (!wellFormed) {
55
+ console.warn(`[web5-theme] Skipping malformed theme override key: ${key}`);
56
+ }
57
+ return wellFormed;
58
+ });
59
+ if (entries.length === 0) {
60
+ existing == null || existing.remove();
61
+ return;
62
+ }
63
+ const style = document.createElement('style');
64
+ style.setAttribute(MARKER_ATTR, '');
65
+ document.head.appendChild(style);
66
+ const sheet = style.sheet;
67
+ if (!sheet) {
68
+ style.remove();
69
+ return;
70
+ }
71
+ sheet.insertRule(`${WEB5_SCOPE} {}`, 0);
72
+ const rule = sheet.cssRules[0];
73
+ for (const [key, value] of entries) {
74
+ try {
75
+ rule.style.setProperty(key, value);
76
+ } catch {
77
+ // An engine that rejects the value leaves the token at its baked
78
+ // default — degraded theming, never broken CSS.
79
+ }
80
+ }
81
+ // Replace-on-reapply: the fresh element is appended (so it stays last in
82
+ // document order) before the stale one is dropped.
83
+ existing == null || existing.remove();
84
+ }
85
+ //# sourceMappingURL=applyThemeOverrides.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["WEB5_SCOPE","THEME_OVERRIDE_TOKENS","Set","KEY_PATTERN","MARKER_ATTR","applyThemeOverrides","overrides","document","existing","head","querySelector","entries","Object","filter","_ref","key","wellFormed","test","console","warn","length","remove","style","createElement","setAttribute","appendChild","sheet","insertRule","rule","cssRules","value","setProperty"],"sources":["../../../src/client/applyThemeOverrides.ts"],"sourcesContent":["/**\n * Runtime theme token injection (DL #131).\n *\n * `Configuration.themeOverrides` carries per-store CSS custom-property\n * overrides (`--primary`, `--radius`, `--heading`, ...) computed from the\n * store's own theme (e.g. the Shopify `settings_data.json` extractor). This\n * applies them to the page so they win over the bundle's baked `theme.css`:\n *\n * - **Scoped, not `:root`**: the rule targets `WEB5_SCOPE`, the same\n * `:is(#root, .w5-root, [data-web5-placement], .debug-view)` selector every\n * compiled bundle stylesheet uses — host-page styling is never touched, and\n * equal specificity + later document order makes the override win. Callers\n * must therefore apply AFTER the client bundle's CSS is in the document.\n * - **Open token set**: any sanely-shaped custom property is applied, so a\n * template can grow new tokens without an FE or BE release. The mandatory\n * `--` prefix means an override can only define custom properties — it can\n * never set a real CSS property inside the scope. The server enforces the\n * same shape (plus value sanitation) at write time.\n * - **Values are inert**: entries are written with\n * `CSSStyleDeclaration.setProperty`, never string-concatenated into CSS, so\n * a hostile value cannot terminate the declaration or open a new rule.\n * - **Idempotent**: one marker element per document, replaced wholesale on\n * re-apply; empty/absent input removes it.\n */\nimport { WEB5_SCOPE } from '../hostScope';\n\n/** A themeOverrides key: always a CSS custom property. */\nexport type ThemeOverrideKey = `--${string}`;\n\n/**\n * Writer-side shape for a theme override map. The catalog below documents\n * the tokens the seed consumes today; the type stays open so new tokens\n * flow end-to-end with no release anywhere but the seed.\n */\nexport type ThemeOverrides = Record<ThemeOverrideKey, string>;\n\n/**\n * The known token catalog — the CSS custom properties the seed's Tailwind\n * bridge consumes today. GUIDANCE, NOT A GATE: writers (the Shopify theme\n * extractor, admin tooling) should stick to these to produce visible effect,\n * but `applyThemeOverrides` deliberately applies any well-formed custom\n * property so the vocabulary can grow from the seed alone (matching the\n * open-set validation on the server).\n */\nexport const THEME_OVERRIDE_TOKENS: ReadonlySet<string> = new Set([\n '--background', '--foreground',\n '--card', '--card-foreground',\n '--popover', '--popover-foreground',\n '--primary', '--primary-foreground',\n '--secondary', '--secondary-foreground',\n '--muted', '--muted-foreground',\n '--accent', '--accent-foreground',\n '--border', '--input', '--ring',\n '--radius',\n '--font-sans', '--font-display',\n '--heading',\n]);\n\n/** Mirrors the server's Consts.ThemeOverrideKeyPattern. */\nconst KEY_PATTERN = /^--[a-z][a-z0-9-]{0,62}$/;\n\nconst MARKER_ATTR = 'data-web5-theme-overrides';\n\nexport function applyThemeOverrides(\n overrides?: Record<string, string> | null,\n): void {\n if (typeof document === 'undefined') return;\n\n const existing = document.head.querySelector(`style[${MARKER_ATTR}]`);\n const entries = Object.entries(overrides ?? {}).filter(([key]) => {\n const wellFormed = KEY_PATTERN.test(key);\n if (!wellFormed) {\n console.warn(`[web5-theme] Skipping malformed theme override key: ${key}`);\n }\n return wellFormed;\n });\n\n if (entries.length === 0) {\n existing?.remove();\n return;\n }\n\n const style = document.createElement('style');\n style.setAttribute(MARKER_ATTR, '');\n document.head.appendChild(style);\n const sheet = style.sheet;\n if (!sheet) {\n style.remove();\n return;\n }\n sheet.insertRule(`${WEB5_SCOPE} {}`, 0);\n const rule = sheet.cssRules[0] as CSSStyleRule;\n for (const [key, value] of entries) {\n try {\n rule.style.setProperty(key, value);\n } catch {\n // An engine that rejects the value leaves the token at its baked\n // default — degraded theming, never broken CSS.\n }\n }\n // Replace-on-reapply: the fresh element is appended (so it stays last in\n // document order) before the stale one is dropped.\n existing?.remove();\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAASA,UAAU,QAAQ,cAAc;;AAEzC;;AAGA;AACA;AACA;AACA;AACA;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,qBAA0C,GAAG,IAAIC,GAAG,CAAC,CAChE,cAAc,EAAE,cAAc,EAC9B,QAAQ,EAAE,mBAAmB,EAC7B,WAAW,EAAE,sBAAsB,EACnC,WAAW,EAAE,sBAAsB,EACnC,aAAa,EAAE,wBAAwB,EACvC,SAAS,EAAE,oBAAoB,EAC/B,UAAU,EAAE,qBAAqB,EACjC,UAAU,EAAE,SAAS,EAAE,QAAQ,EAC/B,UAAU,EACV,aAAa,EAAE,gBAAgB,EAC/B,WAAW,CACZ,CAAC;;AAEF;AACA,MAAMC,WAAW,GAAG,0BAA0B;AAE9C,MAAMC,WAAW,GAAG,2BAA2B;AAE/C,OAAO,SAASC,mBAAmBA,CACjCC,SAAyC,EACnC;EACN,IAAI,OAAOC,QAAQ,KAAK,WAAW,EAAE;EAErC,MAAMC,QAAQ,GAAGD,QAAQ,CAACE,IAAI,CAACC,aAAa,CAAC,SAASN,WAAW,GAAG,CAAC;EACrE,MAAMO,OAAO,GAAGC,MAAM,CAACD,OAAO,CAACL,SAAS,IAAI,CAAC,CAAC,CAAC,CAACO,MAAM,CAACC,IAAA,IAAW;IAAA,IAAV,CAACC,GAAG,CAAC,GAAAD,IAAA;IAC3D,MAAME,UAAU,GAAGb,WAAW,CAACc,IAAI,CAACF,GAAG,CAAC;IACxC,IAAI,CAACC,UAAU,EAAE;MACfE,OAAO,CAACC,IAAI,CAAC,uDAAuDJ,GAAG,EAAE,CAAC;IAC5E;IACA,OAAOC,UAAU;EACnB,CAAC,CAAC;EAEF,IAAIL,OAAO,CAACS,MAAM,KAAK,CAAC,EAAE;IACxBZ,QAAQ,YAARA,QAAQ,CAAEa,MAAM,CAAC,CAAC;IAClB;EACF;EAEA,MAAMC,KAAK,GAAGf,QAAQ,CAACgB,aAAa,CAAC,OAAO,CAAC;EAC7CD,KAAK,CAACE,YAAY,CAACpB,WAAW,EAAE,EAAE,CAAC;EACnCG,QAAQ,CAACE,IAAI,CAACgB,WAAW,CAACH,KAAK,CAAC;EAChC,MAAMI,KAAK,GAAGJ,KAAK,CAACI,KAAK;EACzB,IAAI,CAACA,KAAK,EAAE;IACVJ,KAAK,CAACD,MAAM,CAAC,CAAC;IACd;EACF;EACAK,KAAK,CAACC,UAAU,CAAC,GAAG3B,UAAU,KAAK,EAAE,CAAC,CAAC;EACvC,MAAM4B,IAAI,GAAGF,KAAK,CAACG,QAAQ,CAAC,CAAC,CAAiB;EAC9C,KAAK,MAAM,CAACd,GAAG,EAAEe,KAAK,CAAC,IAAInB,OAAO,EAAE;IAClC,IAAI;MACFiB,IAAI,CAACN,KAAK,CAACS,WAAW,CAAChB,GAAG,EAAEe,KAAK,CAAC;IACpC,CAAC,CAAC,MAAM;MACN;MACA;IAAA;EAEJ;EACA;EACA;EACAtB,QAAQ,YAARA,QAAQ,CAAEa,MAAM,CAAC,CAAC;AACpB","ignoreList":[]}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Client-UMD URL resolution, shared between `web50-server-ui` (the main app)
3
+ * and `embed-placement` (the storefront widget) — DL #094 (per-msid) + DL #129
4
+ * (per-template). Centralized here for the same reason as
5
+ * `clientBundleOverride.ts`: both callers must derive identical URLs, and
6
+ * divergence on which bundle a tenant executes would be a real bug.
7
+ *
8
+ * Two tiers (DL #129):
9
+ * - `templateId` present (DIY store) → the shared per-template bundle,
10
+ * `<cdn>/templates/<id>/production/…`. Every store on a template runs the
11
+ * same artifact; `production` is the promoted channel.
12
+ * - `templateId` absent (customized tier: feature-com, circana, smallflower)
13
+ * → the per-msid bundle at `<cdn>/<msid>/latest/…`, byte-identical to the
14
+ * pre-template behavior.
15
+ */
16
+
17
+ const CLIENT_BUNDLE_CDN = 'https://d13cktzsgh0qls.cloudfront.net';
18
+ const CLIENT_BUNDLE_FILENAME = 'w5-components.umd.js';
19
+
20
+ /**
21
+ * The template catalog the onboarding gallery and the preview picker render —
22
+ * published by web5-seed-1's CI (`templates/manifest.json` + previews),
23
+ * CORS-open. Thumbnail paths inside are relative to `TEMPLATES_CDN_BASE`.
24
+ */
25
+ export const TEMPLATES_CDN_BASE = `${CLIENT_BUNDLE_CDN}/templates`;
26
+ export const TEMPLATES_MANIFEST_URL = `${TEMPLATES_CDN_BASE}/manifest.json`;
27
+
28
+ /**
29
+ * Template ids are kebab-case slugs from `templates/manifest.json`
30
+ * (`web5-seed-1`). The id becomes a CDN path segment, so anything else is a
31
+ * config error — resolve falls back to the per-msid path (fail-soft, and the
32
+ * store keeps rendering) rather than fetching a malformed URL.
33
+ */
34
+ const TEMPLATE_ID_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
35
+ export function isValidTemplateId(id) {
36
+ return id.length <= 64 && TEMPLATE_ID_PATTERN.test(id);
37
+ }
38
+
39
+ /**
40
+ * `?web5Template=<id>` — client-side template override (DL #129 Q10), the
41
+ * mechanism behind the live-preview picker: reload with the param and the page
42
+ * renders under that template's shared bundle, no Configuration write.
43
+ *
44
+ * Strictly narrower than `?clientBundleUrl=`: the value is a kebab-validated
45
+ * id interpolated into the fixed CDN template prefix, so it can only ever
46
+ * select one of our own template bundles — purely presentational, harmless if
47
+ * a visitor sets it by hand. Precedence at the call sites:
48
+ * `clientBundleUrl` override > this > `Configuration.templateId`.
49
+ */
50
+ export function getTemplateOverride(search) {
51
+ const value = new URLSearchParams(search).get('web5Template');
52
+ if (!value) {
53
+ return null;
54
+ }
55
+ if (!isValidTemplateId(value)) {
56
+ console.warn(`[web5-core] Ignoring malformed ?web5Template= override "${value}".`);
57
+ return null;
58
+ }
59
+ return value;
60
+ }
61
+
62
+ /** `?web5TemplatePicker=1` — should the preview picker overlay render? */
63
+ export function isTemplatePickerRequested(search) {
64
+ return new URLSearchParams(search).get('web5TemplatePicker') === '1';
65
+ }
66
+ export function resolveClientBundleUrl(msid, templateId) {
67
+ if (templateId) {
68
+ if (TEMPLATE_ID_PATTERN.test(templateId)) {
69
+ return `${CLIENT_BUNDLE_CDN}/templates/${templateId}/production/${CLIENT_BUNDLE_FILENAME}`;
70
+ }
71
+ console.warn(`[web5-core] Ignoring malformed templateId "${templateId}" — falling back to the per-msid bundle.`);
72
+ }
73
+ return `${CLIENT_BUNDLE_CDN}/${msid}/latest/${CLIENT_BUNDLE_FILENAME}`;
74
+ }
75
+ //# sourceMappingURL=clientBundleUrl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["CLIENT_BUNDLE_CDN","CLIENT_BUNDLE_FILENAME","TEMPLATES_CDN_BASE","TEMPLATES_MANIFEST_URL","TEMPLATE_ID_PATTERN","isValidTemplateId","id","length","test","getTemplateOverride","search","value","URLSearchParams","get","console","warn","isTemplatePickerRequested","resolveClientBundleUrl","msid","templateId"],"sources":["../../../src/client/clientBundleUrl.ts"],"sourcesContent":["/**\n * Client-UMD URL resolution, shared between `web50-server-ui` (the main app)\n * and `embed-placement` (the storefront widget) — DL #094 (per-msid) + DL #129\n * (per-template). Centralized here for the same reason as\n * `clientBundleOverride.ts`: both callers must derive identical URLs, and\n * divergence on which bundle a tenant executes would be a real bug.\n *\n * Two tiers (DL #129):\n * - `templateId` present (DIY store) → the shared per-template bundle,\n * `<cdn>/templates/<id>/production/…`. Every store on a template runs the\n * same artifact; `production` is the promoted channel.\n * - `templateId` absent (customized tier: feature-com, circana, smallflower)\n * → the per-msid bundle at `<cdn>/<msid>/latest/…`, byte-identical to the\n * pre-template behavior.\n */\n\nconst CLIENT_BUNDLE_CDN = 'https://d13cktzsgh0qls.cloudfront.net';\nconst CLIENT_BUNDLE_FILENAME = 'w5-components.umd.js';\n\n/**\n * The template catalog the onboarding gallery and the preview picker render —\n * published by web5-seed-1's CI (`templates/manifest.json` + previews),\n * CORS-open. Thumbnail paths inside are relative to `TEMPLATES_CDN_BASE`.\n */\nexport const TEMPLATES_CDN_BASE = `${CLIENT_BUNDLE_CDN}/templates`;\nexport const TEMPLATES_MANIFEST_URL = `${TEMPLATES_CDN_BASE}/manifest.json`;\n\n/**\n * Template ids are kebab-case slugs from `templates/manifest.json`\n * (`web5-seed-1`). The id becomes a CDN path segment, so anything else is a\n * config error — resolve falls back to the per-msid path (fail-soft, and the\n * store keeps rendering) rather than fetching a malformed URL.\n */\nconst TEMPLATE_ID_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;\n\nexport function isValidTemplateId(id: string): boolean {\n return id.length <= 64 && TEMPLATE_ID_PATTERN.test(id);\n}\n\n/**\n * `?web5Template=<id>` — client-side template override (DL #129 Q10), the\n * mechanism behind the live-preview picker: reload with the param and the page\n * renders under that template's shared bundle, no Configuration write.\n *\n * Strictly narrower than `?clientBundleUrl=`: the value is a kebab-validated\n * id interpolated into the fixed CDN template prefix, so it can only ever\n * select one of our own template bundles — purely presentational, harmless if\n * a visitor sets it by hand. Precedence at the call sites:\n * `clientBundleUrl` override > this > `Configuration.templateId`.\n */\nexport function getTemplateOverride(search: string): string | null {\n const value = new URLSearchParams(search).get('web5Template');\n if (!value) {\n return null;\n }\n if (!isValidTemplateId(value)) {\n console.warn(\n `[web5-core] Ignoring malformed ?web5Template= override \"${value}\".`,\n );\n return null;\n }\n return value;\n}\n\n/** `?web5TemplatePicker=1` — should the preview picker overlay render? */\nexport function isTemplatePickerRequested(search: string): boolean {\n return new URLSearchParams(search).get('web5TemplatePicker') === '1';\n}\n\nexport function resolveClientBundleUrl(\n msid: string,\n templateId?: string | null,\n): string {\n if (templateId) {\n if (TEMPLATE_ID_PATTERN.test(templateId)) {\n return `${CLIENT_BUNDLE_CDN}/templates/${templateId}/production/${CLIENT_BUNDLE_FILENAME}`;\n }\n console.warn(\n `[web5-core] Ignoring malformed templateId \"${templateId}\" — falling back to the per-msid bundle.`,\n );\n }\n return `${CLIENT_BUNDLE_CDN}/${msid}/latest/${CLIENT_BUNDLE_FILENAME}`;\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,MAAMA,iBAAiB,GAAG,uCAAuC;AACjE,MAAMC,sBAAsB,GAAG,sBAAsB;;AAErD;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,kBAAkB,GAAG,GAAGF,iBAAiB,YAAY;AAClE,OAAO,MAAMG,sBAAsB,GAAG,GAAGD,kBAAkB,gBAAgB;;AAE3E;AACA;AACA;AACA;AACA;AACA;AACA,MAAME,mBAAmB,GAAG,0BAA0B;AAEtD,OAAO,SAASC,iBAAiBA,CAACC,EAAU,EAAW;EACrD,OAAOA,EAAE,CAACC,MAAM,IAAI,EAAE,IAAIH,mBAAmB,CAACI,IAAI,CAACF,EAAE,CAAC;AACxD;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASG,mBAAmBA,CAACC,MAAc,EAAiB;EACjE,MAAMC,KAAK,GAAG,IAAIC,eAAe,CAACF,MAAM,CAAC,CAACG,GAAG,CAAC,cAAc,CAAC;EAC7D,IAAI,CAACF,KAAK,EAAE;IACV,OAAO,IAAI;EACb;EACA,IAAI,CAACN,iBAAiB,CAACM,KAAK,CAAC,EAAE;IAC7BG,OAAO,CAACC,IAAI,CACV,2DAA2DJ,KAAK,IAClE,CAAC;IACD,OAAO,IAAI;EACb;EACA,OAAOA,KAAK;AACd;;AAEA;AACA,OAAO,SAASK,yBAAyBA,CAACN,MAAc,EAAW;EACjE,OAAO,IAAIE,eAAe,CAACF,MAAM,CAAC,CAACG,GAAG,CAAC,oBAAoB,CAAC,KAAK,GAAG;AACtE;AAEA,OAAO,SAASI,sBAAsBA,CACpCC,IAAY,EACZC,UAA0B,EAClB;EACR,IAAIA,UAAU,EAAE;IACd,IAAIf,mBAAmB,CAACI,IAAI,CAACW,UAAU,CAAC,EAAE;MACxC,OAAO,GAAGnB,iBAAiB,cAAcmB,UAAU,eAAelB,sBAAsB,EAAE;IAC5F;IACAa,OAAO,CAACC,IAAI,CACV,8CAA8CI,UAAU,0CAC1D,CAAC;EACH;EACA,OAAO,GAAGnB,iBAAiB,IAAIkB,IAAI,WAAWjB,sBAAsB,EAAE;AACxE","ignoreList":[]}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Universal client-config merge (DL #129 Q3/Q7): the loaded client bundle's
3
+ * `clientConfig` is the default; the backend `Configuration.clientConfig`
4
+ * overrides it **per field, only where a field was actually returned**.
5
+ *
6
+ * One rule for both tiers, shared between `web50-server-ui` and
7
+ * `embed-placement` (the two places that load `w5-components.umd.js`):
8
+ * - Customized-tier clients (feature-com, circana): the backend returns no
9
+ * `clientConfig`, so the merge is identity — the bundle's baked config
10
+ * passes through byte-identical (same reference, see spec).
11
+ * - DIY/template stores: the backend supplies per-store deltas that grow
12
+ * field-by-field as the client-config port (Phase 3b) lands; each ported
13
+ * field takes effect here with no FE change.
14
+ *
15
+ * Semantics — presence, not truthiness:
16
+ * - The wire omits unset proto fields, so key-absence means "not returned"
17
+ * → bundle value kept. A key that IS present wins even when falsy
18
+ * (`''`, `false`, `0`) — it was explicitly set.
19
+ * - `undefined`/`null` values are treated as absent (proto3 JSON never
20
+ * emits null for wrapper types; defensive against hand-written configs).
21
+ * - Plain objects merge per leaf; arrays replace wholesale; class
22
+ * instances / functions replace wholesale.
23
+ */
24
+
25
+ function isPlainObject(value) {
26
+ return typeof value === 'object' && value !== null && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
27
+ }
28
+ export function mergeClientConfig(bundleConfig,
29
+ // `Record<string, unknown>` admitted alongside `DeepPartial<T>`: the wire
30
+ // value is untyped JSON (`Configuration.clientConfig`), so callers hold it
31
+ // as a plain record — the runtime presence checks do the validation.
32
+ backendConfig) {
33
+ if (backendConfig === undefined || backendConfig === null) {
34
+ return bundleConfig;
35
+ }
36
+ if (!isPlainObject(backendConfig)) {
37
+ return bundleConfig;
38
+ }
39
+ const keys = Object.keys(backendConfig).filter(key => backendConfig[key] !== undefined && backendConfig[key] !== null);
40
+ // Empty delta → identity, same reference: the byte-identical passthrough
41
+ // guarantee for clients whose Configuration carries no clientConfig.
42
+ if (keys.length === 0) {
43
+ return bundleConfig;
44
+ }
45
+ const result = {
46
+ ...bundleConfig
47
+ };
48
+ for (const key of keys) {
49
+ const backendValue = backendConfig[key];
50
+ const bundleValue = bundleConfig[key];
51
+ result[key] = isPlainObject(backendValue) && isPlainObject(bundleValue) ? mergeClientConfig(bundleValue, backendValue) : backendValue;
52
+ }
53
+ return result;
54
+ }
55
+ //# sourceMappingURL=mergeClientConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["isPlainObject","value","Array","isArray","Object","getPrototypeOf","prototype","mergeClientConfig","bundleConfig","backendConfig","undefined","keys","filter","key","length","result","backendValue","bundleValue"],"sources":["../../../src/client/mergeClientConfig.ts"],"sourcesContent":["/**\n * Universal client-config merge (DL #129 Q3/Q7): the loaded client bundle's\n * `clientConfig` is the default; the backend `Configuration.clientConfig`\n * overrides it **per field, only where a field was actually returned**.\n *\n * One rule for both tiers, shared between `web50-server-ui` and\n * `embed-placement` (the two places that load `w5-components.umd.js`):\n * - Customized-tier clients (feature-com, circana): the backend returns no\n * `clientConfig`, so the merge is identity — the bundle's baked config\n * passes through byte-identical (same reference, see spec).\n * - DIY/template stores: the backend supplies per-store deltas that grow\n * field-by-field as the client-config port (Phase 3b) lands; each ported\n * field takes effect here with no FE change.\n *\n * Semantics — presence, not truthiness:\n * - The wire omits unset proto fields, so key-absence means \"not returned\"\n * → bundle value kept. A key that IS present wins even when falsy\n * (`''`, `false`, `0`) — it was explicitly set.\n * - `undefined`/`null` values are treated as absent (proto3 JSON never\n * emits null for wrapper types; defensive against hand-written configs).\n * - Plain objects merge per leaf; arrays replace wholesale; class\n * instances / functions replace wholesale.\n */\n\nexport type DeepPartial<T> = T extends (infer U)[]\n ? U[]\n : T extends object\n ? { [K in keyof T]?: DeepPartial<T[K]> }\n : T;\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value) &&\n (Object.getPrototypeOf(value) === Object.prototype ||\n Object.getPrototypeOf(value) === null)\n );\n}\n\nexport function mergeClientConfig<T extends object>(\n bundleConfig: T,\n // `Record<string, unknown>` admitted alongside `DeepPartial<T>`: the wire\n // value is untyped JSON (`Configuration.clientConfig`), so callers hold it\n // as a plain record — the runtime presence checks do the validation.\n backendConfig?: DeepPartial<T> | Record<string, unknown> | null,\n): T {\n if (backendConfig === undefined || backendConfig === null) {\n return bundleConfig;\n }\n if (!isPlainObject(backendConfig)) {\n return bundleConfig;\n }\n const keys = Object.keys(backendConfig).filter(\n (key) => (backendConfig as Record<string, unknown>)[key] !== undefined &&\n (backendConfig as Record<string, unknown>)[key] !== null,\n );\n // Empty delta → identity, same reference: the byte-identical passthrough\n // guarantee for clients whose Configuration carries no clientConfig.\n if (keys.length === 0) {\n return bundleConfig;\n }\n\n const result: Record<string, unknown> = {\n ...(bundleConfig as Record<string, unknown>),\n };\n for (const key of keys) {\n const backendValue = (backendConfig as Record<string, unknown>)[key];\n const bundleValue = (bundleConfig as Record<string, unknown>)[key];\n result[key] =\n isPlainObject(backendValue) && isPlainObject(bundleValue)\n ? mergeClientConfig(bundleValue, backendValue)\n : backendValue;\n }\n return result as T;\n}\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAQA,SAASA,aAAaA,CAACC,KAAc,EAAoC;EACvE,OACE,OAAOA,KAAK,KAAK,QAAQ,IACzBA,KAAK,KAAK,IAAI,IACd,CAACC,KAAK,CAACC,OAAO,CAACF,KAAK,CAAC,KACpBG,MAAM,CAACC,cAAc,CAACJ,KAAK,CAAC,KAAKG,MAAM,CAACE,SAAS,IAChDF,MAAM,CAACC,cAAc,CAACJ,KAAK,CAAC,KAAK,IAAI,CAAC;AAE5C;AAEA,OAAO,SAASM,iBAAiBA,CAC/BC,YAAe;AACf;AACA;AACA;AACAC,aAA+D,EAC5D;EACH,IAAIA,aAAa,KAAKC,SAAS,IAAID,aAAa,KAAK,IAAI,EAAE;IACzD,OAAOD,YAAY;EACrB;EACA,IAAI,CAACR,aAAa,CAACS,aAAa,CAAC,EAAE;IACjC,OAAOD,YAAY;EACrB;EACA,MAAMG,IAAI,GAAGP,MAAM,CAACO,IAAI,CAACF,aAAa,CAAC,CAACG,MAAM,CAC3CC,GAAG,IAAMJ,aAAa,CAA6BI,GAAG,CAAC,KAAKH,SAAS,IACnED,aAAa,CAA6BI,GAAG,CAAC,KAAK,IACxD,CAAC;EACD;EACA;EACA,IAAIF,IAAI,CAACG,MAAM,KAAK,CAAC,EAAE;IACrB,OAAON,YAAY;EACrB;EAEA,MAAMO,MAA+B,GAAG;IACtC,GAAIP;EACN,CAAC;EACD,KAAK,MAAMK,GAAG,IAAIF,IAAI,EAAE;IACtB,MAAMK,YAAY,GAAIP,aAAa,CAA6BI,GAAG,CAAC;IACpE,MAAMI,WAAW,GAAIT,YAAY,CAA6BK,GAAG,CAAC;IAClEE,MAAM,CAACF,GAAG,CAAC,GACTb,aAAa,CAACgB,YAAY,CAAC,IAAIhB,aAAa,CAACiB,WAAW,CAAC,GACrDV,iBAAiB,CAACU,WAAW,EAAED,YAAY,CAAC,GAC5CA,YAAY;EACpB;EACA,OAAOD,MAAM;AACf","ignoreList":[]}
@@ -0,0 +1,154 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ import { headingText, entitySectionBodyText, extractEntityRefs, calloutFromParts } from './parse-utils.js';
3
+ import { DIAGNOSTIC_TYPES } from '../diagnosticTypes.js';
4
+
5
+ /**
6
+ * The semantic entity type this definition claims — the `<type>` segment of
7
+ * `web5://entity/<type>/<id>` (see the client `entityConfig.entityTypes` map).
8
+ */
9
+ const COLLECTION_ENTITY_TYPE = 'collection';
10
+
11
+ /**
12
+ * Entity section restricted to **collection** entities
13
+ * (`web5://entity/collection/<id>`).
14
+ *
15
+ * Same markdown shapes as {@link EntitySectionDefinition} — an `h2`, optional
16
+ * intro/callout, then entity links either at the top level or as list items —
17
+ * but every entity link must be a collection. That makes it a strictly narrower
18
+ * match, so it MUST be registered **before** `EntitySectionDefinition` (which
19
+ * claims any `link[entityType]`, i.e. product and article sections too). Once
20
+ * `EntitySectionDefinition` matches, the block is consumed and no later
21
+ * definition is consulted.
22
+ *
23
+ * Mixed blocks (a collection next to a product or an article) fall through:
24
+ * the patterns only cover consecutive collection links, and `parse()` returns
25
+ * `null` when a non-collection entity slipped into the matched slice, handing
26
+ * the block to `EntitySectionDefinition`.
27
+ *
28
+ * Props are the shared {@link EntityCompProps} shape, so a client renders it
29
+ * with a dedicated `EntityCollectionSection` component while product/article
30
+ * sections keep using their own components under `EntitySectionDefinition`.
31
+ */
32
+ export class EntityCollectionSectionDefinition {
33
+ constructor() {
34
+ _defineProperty(this, "sectionType", 'entityCollection');
35
+ /**
36
+ * One pattern, matching the shape the orchestrator actually emits for a
37
+ * collections block:
38
+ *
39
+ * <!-- section:entities, semantic:items -->
40
+ * ## Curated collections by theme
41
+ * - [](web5://entity/collection/<id>) description
42
+ *
43
+ * The head absorbs the section descriptor and any intro copy or ECHO
44
+ * callout; each list item is a collection link, an optional markdown image,
45
+ * then its description and optional per-item callout. A trailing
46
+ * section-level callout is intentionally left outside the pattern so it
47
+ * falls through to the downstream CalloutSection.
48
+ *
49
+ * Deliberately narrow: anything else (collection links at the top level
50
+ * rather than in a list, a block mixing entity types) falls through to
51
+ * `EntitySectionDefinition`, which is what handled it before this
52
+ * definition existed.
53
+ */
54
+ _defineProperty(this, "patterns", ['html_comment* > h2 > (html_comment | t | callout)* > list{item[1-]:link[entityType="collection"] > image? > (t | callout)*}']);
55
+ _defineProperty(this, "defaults", {
56
+ title: 'Collections',
57
+ items: [{
58
+ entityId: 'example-1',
59
+ entityType: COLLECTION_ENTITY_TYPE,
60
+ entityUrl: 'web5://entity/collection/example-1',
61
+ linkText: 'Example collection'
62
+ }]
63
+ });
64
+ _defineProperty(this, "fixtures", [{
65
+ markdown: '## Shop the collections\n\nCurated edits for the season.\n\n- [Running](web5://entity/collection/pUx3mVQ1TmyN2mVYQqQ8Jg) Shoes and kit built for the road\n- [Trail](web5://entity/collection/Rr8kQyO0S1uH2nQ0mQ2AqQ) Grip and protection off-road',
66
+ expected: {
67
+ title: 'Shop the collections',
68
+ description: 'Curated edits for the season.',
69
+ items: [{
70
+ entityId: 'pUx3mVQ1TmyN2mVYQqQ8Jg',
71
+ entityType: 'collection',
72
+ entityUrl: 'web5://entity/collection/pUx3mVQ1TmyN2mVYQqQ8Jg',
73
+ linkText: 'Running',
74
+ description: 'Shoes and kit built for the road'
75
+ }, {
76
+ entityId: 'Rr8kQyO0S1uH2nQ0mQ2AqQ',
77
+ entityType: 'collection',
78
+ entityUrl: 'web5://entity/collection/Rr8kQyO0S1uH2nQ0mQ2AqQ',
79
+ linkText: 'Trail',
80
+ description: 'Grip and protection off-road'
81
+ }]
82
+ }
83
+ }, {
84
+ // The shape the orchestrator emits: a section descriptor, empty link
85
+ // text, and a Shopify gid (which is itself a `://` url) as the entity id.
86
+ markdown: '<!-- section:entities, semantic:items -->\n## Curated collections by theme\n- [](web5://entity/collection/gid://shopify/Collection/347895267466) storewide assortment spanning the full product range\n- [](web5://entity/collection/gid://shopify/Collection/348001501322) artistic **Watercolor** candles with bright vessels',
87
+ expected: {
88
+ title: 'Curated collections by theme',
89
+ items: [{
90
+ entityId: 'gid://shopify/Collection/347895267466',
91
+ entityType: 'collection',
92
+ entityUrl: 'web5://entity/collection/gid://shopify/Collection/347895267466',
93
+ description: 'storewide assortment spanning the full product range'
94
+ }, {
95
+ entityId: 'gid://shopify/Collection/348001501322',
96
+ entityType: 'collection',
97
+ entityUrl: 'web5://entity/collection/gid://shopify/Collection/348001501322',
98
+ // Inline emphasis survives — descriptions keep their markdown.
99
+ description: 'artistic **Watercolor** candles with bright vessels'
100
+ }]
101
+ }
102
+ }, {
103
+ // Single collection — the full-width banner shape.
104
+ markdown: '## Winter essentials\n\n- [Shop winter](web5://entity/collection/aB1cD2eF3gH4iJ5kL6mN7o) Layers for the cold months',
105
+ expected: {
106
+ title: 'Winter essentials',
107
+ items: [{
108
+ entityId: 'aB1cD2eF3gH4iJ5kL6mN7o',
109
+ entityType: 'collection',
110
+ entityUrl: 'web5://entity/collection/aB1cD2eF3gH4iJ5kL6mN7o',
111
+ linkText: 'Shop winter',
112
+ description: 'Layers for the cold months'
113
+ }]
114
+ }
115
+ }]);
116
+ }
117
+ parse(parts, context) {
118
+ const entityRefs = extractEntityRefs(parts);
119
+ if (entityRefs.length === 0) {
120
+ context == null || context.reportDiagnostic == null || context.reportDiagnostic(DIAGNOSTIC_TYPES.SECTION_REJECTED, 'entityCollection: no entity links found');
121
+ return null;
122
+ }
123
+
124
+ // Guard the pattern: anything that is not a collection means this block
125
+ // belongs to the generic entity section, not here.
126
+ if (entityRefs.some(e => e.entityType !== COLLECTION_ENTITY_TYPE)) {
127
+ context == null || context.reportDiagnostic == null || context.reportDiagnostic(DIAGNOSTIC_TYPES.SECTION_REJECTED, 'entityCollection: block mixes collections with other entity types');
128
+ return null;
129
+ }
130
+ const title = headingText(parts, 2) || 'Collections';
131
+ const description = entitySectionBodyText(parts) || undefined;
132
+
133
+ // Every entity link lives inside the list, so any top-level callout is
134
+ // section-level by construction — per-item callouts are nested in the list
135
+ // items and are picked up by extractEntityRefs instead.
136
+ const callout = calloutFromParts(parts);
137
+ const items = entityRefs.map(e => ({
138
+ entityId: e.entityId,
139
+ entityType: e.entityType,
140
+ entityUrl: e.entityUrl,
141
+ linkText: e.linkText || undefined,
142
+ description: e.description || undefined,
143
+ imageUrl: e.markdownImageUrl,
144
+ callout: e.callout
145
+ }));
146
+ return {
147
+ title,
148
+ description,
149
+ callout,
150
+ items
151
+ };
152
+ }
153
+ }
154
+ //# sourceMappingURL=EntityCollectionSectionDefinition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["headingText","entitySectionBodyText","extractEntityRefs","calloutFromParts","DIAGNOSTIC_TYPES","COLLECTION_ENTITY_TYPE","EntityCollectionSectionDefinition","constructor","_defineProperty","title","items","entityId","entityType","entityUrl","linkText","markdown","expected","description","parse","parts","context","entityRefs","length","reportDiagnostic","SECTION_REJECTED","some","e","undefined","callout","map","imageUrl","markdownImageUrl"],"sources":["../../../../src/component/componentDefinitions/EntityCollectionSectionDefinition.ts"],"sourcesContent":["import {\n headingText,\n entitySectionBodyText,\n extractEntityRefs,\n calloutFromParts,\n} from './parse-utils';\nimport type { Part } from '../../parts/parts';\nimport type {\n EntityCompProps,\n EntityItem,\n EntitySectionComponent,\n SectionFixture,\n} from '../types';\nimport type { SectionDefinition, ParseContext } from '../section-definition';\nimport { DIAGNOSTIC_TYPES } from '../diagnosticTypes';\n\n/**\n * The semantic entity type this definition claims — the `<type>` segment of\n * `web5://entity/<type>/<id>` (see the client `entityConfig.entityTypes` map).\n */\nconst COLLECTION_ENTITY_TYPE = 'collection';\n\n/**\n * Entity section restricted to **collection** entities\n * (`web5://entity/collection/<id>`).\n *\n * Same markdown shapes as {@link EntitySectionDefinition} — an `h2`, optional\n * intro/callout, then entity links either at the top level or as list items —\n * but every entity link must be a collection. That makes it a strictly narrower\n * match, so it MUST be registered **before** `EntitySectionDefinition` (which\n * claims any `link[entityType]`, i.e. product and article sections too). Once\n * `EntitySectionDefinition` matches, the block is consumed and no later\n * definition is consulted.\n *\n * Mixed blocks (a collection next to a product or an article) fall through:\n * the patterns only cover consecutive collection links, and `parse()` returns\n * `null` when a non-collection entity slipped into the matched slice, handing\n * the block to `EntitySectionDefinition`.\n *\n * Props are the shared {@link EntityCompProps} shape, so a client renders it\n * with a dedicated `EntityCollectionSection` component while product/article\n * sections keep using their own components under `EntitySectionDefinition`.\n */\nexport class EntityCollectionSectionDefinition\n implements SectionDefinition<EntitySectionComponent>\n{\n sectionType = 'entityCollection' as const;\n /**\n * One pattern, matching the shape the orchestrator actually emits for a\n * collections block:\n *\n * <!-- section:entities, semantic:items -->\n * ## Curated collections by theme\n * - [](web5://entity/collection/<id>) description\n *\n * The head absorbs the section descriptor and any intro copy or ECHO\n * callout; each list item is a collection link, an optional markdown image,\n * then its description and optional per-item callout. A trailing\n * section-level callout is intentionally left outside the pattern so it\n * falls through to the downstream CalloutSection.\n *\n * Deliberately narrow: anything else (collection links at the top level\n * rather than in a list, a block mixing entity types) falls through to\n * `EntitySectionDefinition`, which is what handled it before this\n * definition existed.\n */\n patterns = [\n 'html_comment* > h2 > (html_comment | t | callout)* > list{item[1-]:link[entityType=\"collection\"] > image? > (t | callout)*}',\n ];\n\n defaults: EntityCompProps = {\n title: 'Collections',\n items: [\n {\n entityId: 'example-1',\n entityType: COLLECTION_ENTITY_TYPE,\n entityUrl: 'web5://entity/collection/example-1',\n linkText: 'Example collection',\n },\n ],\n };\n\n fixtures: SectionFixture<EntityCompProps>[] = [\n {\n markdown:\n '## Shop the collections\\n\\nCurated edits for the season.\\n\\n- [Running](web5://entity/collection/pUx3mVQ1TmyN2mVYQqQ8Jg) Shoes and kit built for the road\\n- [Trail](web5://entity/collection/Rr8kQyO0S1uH2nQ0mQ2AqQ) Grip and protection off-road',\n expected: {\n title: 'Shop the collections',\n description: 'Curated edits for the season.',\n items: [\n {\n entityId: 'pUx3mVQ1TmyN2mVYQqQ8Jg',\n entityType: 'collection',\n entityUrl: 'web5://entity/collection/pUx3mVQ1TmyN2mVYQqQ8Jg',\n linkText: 'Running',\n description: 'Shoes and kit built for the road',\n },\n {\n entityId: 'Rr8kQyO0S1uH2nQ0mQ2AqQ',\n entityType: 'collection',\n entityUrl: 'web5://entity/collection/Rr8kQyO0S1uH2nQ0mQ2AqQ',\n linkText: 'Trail',\n description: 'Grip and protection off-road',\n },\n ],\n },\n },\n {\n // The shape the orchestrator emits: a section descriptor, empty link\n // text, and a Shopify gid (which is itself a `://` url) as the entity id.\n markdown:\n '<!-- section:entities, semantic:items -->\\n## Curated collections by theme\\n- [](web5://entity/collection/gid://shopify/Collection/347895267466) storewide assortment spanning the full product range\\n- [](web5://entity/collection/gid://shopify/Collection/348001501322) artistic **Watercolor** candles with bright vessels',\n expected: {\n title: 'Curated collections by theme',\n items: [\n {\n entityId: 'gid://shopify/Collection/347895267466',\n entityType: 'collection',\n entityUrl:\n 'web5://entity/collection/gid://shopify/Collection/347895267466',\n description: 'storewide assortment spanning the full product range',\n },\n {\n entityId: 'gid://shopify/Collection/348001501322',\n entityType: 'collection',\n entityUrl:\n 'web5://entity/collection/gid://shopify/Collection/348001501322',\n // Inline emphasis survives — descriptions keep their markdown.\n description: 'artistic **Watercolor** candles with bright vessels',\n },\n ],\n },\n },\n {\n // Single collection — the full-width banner shape.\n markdown:\n '## Winter essentials\\n\\n- [Shop winter](web5://entity/collection/aB1cD2eF3gH4iJ5kL6mN7o) Layers for the cold months',\n expected: {\n title: 'Winter essentials',\n items: [\n {\n entityId: 'aB1cD2eF3gH4iJ5kL6mN7o',\n entityType: 'collection',\n entityUrl: 'web5://entity/collection/aB1cD2eF3gH4iJ5kL6mN7o',\n linkText: 'Shop winter',\n description: 'Layers for the cold months',\n },\n ],\n },\n },\n ];\n\n parse(parts: Part[], context?: ParseContext): EntityCompProps | null {\n const entityRefs = extractEntityRefs(parts);\n\n if (entityRefs.length === 0) {\n context?.reportDiagnostic?.(\n DIAGNOSTIC_TYPES.SECTION_REJECTED,\n 'entityCollection: no entity links found',\n );\n return null;\n }\n\n // Guard the pattern: anything that is not a collection means this block\n // belongs to the generic entity section, not here.\n if (entityRefs.some((e) => e.entityType !== COLLECTION_ENTITY_TYPE)) {\n context?.reportDiagnostic?.(\n DIAGNOSTIC_TYPES.SECTION_REJECTED,\n 'entityCollection: block mixes collections with other entity types',\n );\n return null;\n }\n\n const title = headingText(parts, 2) || 'Collections';\n const description = entitySectionBodyText(parts) || undefined;\n\n // Every entity link lives inside the list, so any top-level callout is\n // section-level by construction — per-item callouts are nested in the list\n // items and are picked up by extractEntityRefs instead.\n const callout = calloutFromParts(parts);\n\n const items: EntityItem[] = entityRefs.map((e) => ({\n entityId: e.entityId,\n entityType: e.entityType,\n entityUrl: e.entityUrl,\n linkText: e.linkText || undefined,\n description: e.description || undefined,\n imageUrl: e.markdownImageUrl,\n callout: e.callout,\n }));\n\n return { title, description, callout, items };\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -1,5 +1,5 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
- import { headingText, bodyText, firstImageSrc, parseCalloutTag, extractEntityRefs } from './parse-utils.js';
2
+ import { headingText, headingMarkdown, bodyText, firstImageSrc, parseCalloutTag, extractEntityRefs } from './parse-utils.js';
3
3
  import { getPartsByType } from '../../parts/parts.js';
4
4
  import { DIAGNOSTIC_TYPES } from '../diagnosticTypes.js';
5
5
 
@@ -49,7 +49,7 @@ Nike offerings span performance and lifestyle lines.
49
49
  * [Nike Life Cable Knit Turtleneck Sweater](web5://entity/product/ZDvHxsa3IJA8CyGX8BNpdw)
50
50
  * [Nike x Jacquemus NRG HE Swoosh Hoodie](web5://entity/product/qH2lqINGneHWYVZzzGnXTA)`,
51
51
  expected: {
52
- headline: 'Nike collections',
52
+ headline: '**Nike** collections',
53
53
  content: 'Nike offerings span performance and lifestyle lines.',
54
54
  image: 'web5://image/Nike+Life+collection',
55
55
  items: [{
@@ -112,7 +112,10 @@ Nike offerings span performance and lifestyle lines.
112
112
  leadingQuote = parseCalloutTag(raw).text || undefined;
113
113
  }
114
114
  return {
115
- headline: headingText(parts, 1),
115
+ // Markdown-preserving (not headingText): the hero title now carries the
116
+ // bold+italic hook the ECHO callout used to own, so the FE needs the
117
+ // formatting, not just the plain text. See FeatureHeroEntity.tsx.
118
+ headline: headingMarkdown(parts, 1),
116
119
  badge: headingText(parts, 3) || undefined,
117
120
  content: bodyText(parts) || undefined,
118
121
  image: firstImageSrc(parts),
@@ -1 +1 @@
1
- {"version":3,"names":["headingText","bodyText","firstImageSrc","parseCalloutTag","extractEntityRefs","getPartsByType","DIAGNOSTIC_TYPES","HeroEntitySectionDefinition","constructor","_defineProperty","headline","content","items","entityId","entityType","entityUrl","linkText","markdown","expected","image","parse","parts","context","entityRefs","length","reportDiagnostic","SECTION_REJECTED","map","e","undefined","description","imageUrl","markdownImageUrl","callout","h1Index","findIndex","p","_p$meta","type","meta","level","blockquotes","leadingQuote","bq","_bq$meta","_bq$content","bqIndex","indexOf","raw","trim","text","badge"],"sources":["../../../../src/component/componentDefinitions/HeroEntitySectionDefinition.ts"],"sourcesContent":["import {\n headingText,\n bodyText,\n firstImageSrc,\n parseCalloutTag,\n extractEntityRefs,\n} from './parse-utils';\nimport type { Part } from '../../parts/parts';\nimport { getPartsByType } from '../../parts/parts';\nimport type {\n EntityItem,\n HeroEntityCompProps,\n HeroEntitySectionComponent,\n SectionFixture,\n} from '../types';\nimport type { SectionDefinition, ParseContext } from '../section-definition';\nimport { DIAGNOSTIC_TYPES } from '../diagnosticTypes';\n\n/**\n * Hero section whose body carries one or more web5 entity links\n * (`web5://entity/...`). It mirrors the framing of {@link HeroSectionDefinition}\n * (leading callout, h1, image, intro text) but, instead of turning the links\n * into CTA buttons, surfaces them as resolvable entities.\n *\n * Registered ahead of `HeroSectionDefinition` so an entity-bearing hero is\n * claimed here first. The pattern requires at least one `link[entityType]`, and\n * `parse()` returns `null` when no entity refs are found, letting a plain hero\n * fall through to `HeroSectionDefinition`.\n */\nexport class HeroEntitySectionDefinition\n implements SectionDefinition<HeroEntitySectionComponent>\n{\n sectionType = 'hero-entity' as const;\n singleInstance = true;\n patterns = [\n // Leading slots mirror HeroSectionDefinition: section descriptor, then the\n // optional leading ECHO callout, then an optional h3 badge, then the h1.\n // Callout and badge are independent; the second `html_comment?` preserves the\n // legacy callout-before-descriptor ordering for pages saved before this change.\n // Entity links carried as list items (the canonical shape).\n 'html_comment? > callout? > html_comment? > h3? > h1 > (image | t | link)* > list{item[1-]:link[entityType] > image? > (t | callout)*}',\n // Entity links sitting at the top level of the hero body.\n 'html_comment? > callout? > html_comment? > h3? > h1 > (image | t)* > (link[entityType] > image? > (t | callout)*)[1-] > list?',\n ];\n\n defaults: HeroEntityCompProps = {\n headline: 'Featured collection',\n content: 'Explore the highlights from this collection.',\n items: [\n {\n entityId: 'example-1',\n entityType: 'product',\n entityUrl: 'web5://entity/product/example-1',\n linkText: 'Example product',\n },\n ],\n };\n\n fixtures: SectionFixture<HeroEntityCompProps>[] = [\n {\n markdown: `<!-- section:hero, id:hero, semantic:hero -->\n\n# **Nike** collections\n\n![Nike Life collection](web5://image/Nike+Life+collection)\nNike offerings span performance and lifestyle lines.\n\n* [Nike Throwback Future Collection](web5://entity/article/WAyuiC63OSjwpvnOyZcblQ)\n* [Nike Life Cable Knit Turtleneck Sweater](web5://entity/product/ZDvHxsa3IJA8CyGX8BNpdw)\n* [Nike x Jacquemus NRG HE Swoosh Hoodie](web5://entity/product/qH2lqINGneHWYVZzzGnXTA)`,\n expected: {\n headline: 'Nike collections',\n content: 'Nike offerings span performance and lifestyle lines.',\n image: 'web5://image/Nike+Life+collection',\n items: [\n {\n entityId: 'WAyuiC63OSjwpvnOyZcblQ',\n entityType: 'article',\n entityUrl:\n 'web5://entity/article/WAyuiC63OSjwpvnOyZcblQ',\n linkText: 'Nike Throwback Future Collection',\n },\n {\n entityId: 'ZDvHxsa3IJA8CyGX8BNpdw',\n entityType: 'product',\n entityUrl:\n 'web5://entity/product/ZDvHxsa3IJA8CyGX8BNpdw',\n linkText: 'Nike Life Cable Knit Turtleneck Sweater',\n },\n {\n entityId: 'qH2lqINGneHWYVZzzGnXTA',\n entityType: 'product',\n entityUrl:\n 'web5://entity/product/qH2lqINGneHWYVZzzGnXTA',\n linkText: 'Nike x Jacquemus NRG HE Swoosh Hoodie',\n },\n ],\n },\n },\n ];\n\n parse(parts: Part[], context?: ParseContext): HeroEntityCompProps | null {\n const entityRefs = extractEntityRefs(parts);\n\n if (entityRefs.length === 0) {\n // Not an entity hero — let HeroSectionDefinition claim it.\n context?.reportDiagnostic?.(\n DIAGNOSTIC_TYPES.SECTION_REJECTED,\n 'hero-entity: no entity links found',\n );\n return null;\n }\n\n const items: EntityItem[] = entityRefs.map((e) => ({\n entityId: e.entityId,\n entityType: e.entityType,\n entityUrl: e.entityUrl,\n linkText: e.linkText || undefined,\n description: e.description || undefined,\n imageUrl: e.markdownImageUrl,\n callout: e.callout,\n }));\n\n // The leading callout before the h1 — the ECHO the orchestrator emits as the\n // hero's opening line, right after the section descriptor — is part of the\n // hero, mirroring HeroSectionDefinition. A trailing callout falls through to a\n // downstream callout section. (Per-item callouts inside the entity list are\n // handled by extractEntityRefs above and are unaffected.)\n const h1Index = parts.findIndex(\n (p) =>\n p.type === 'heading' && (p.meta as { level?: number })?.level === 1,\n );\n const blockquotes = getPartsByType<Part>(parts, 'callout');\n let leadingQuote: string | undefined;\n\n for (const bq of blockquotes) {\n const bqIndex = parts.indexOf(bq);\n if (h1Index >= 0 && bqIndex > h1Index) {\n continue;\n }\n const raw = (bq.meta?.markdown as string)?.trim() || bq.content?.trim();\n if (!raw) {\n continue;\n }\n leadingQuote = parseCalloutTag(raw).text || undefined;\n }\n\n return {\n headline: headingText(parts, 1),\n badge: headingText(parts, 3) || undefined,\n content: bodyText(parts) || undefined,\n image: firstImageSrc(parts),\n leadingQuote,\n items,\n };\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["headingText","headingMarkdown","bodyText","firstImageSrc","parseCalloutTag","extractEntityRefs","getPartsByType","DIAGNOSTIC_TYPES","HeroEntitySectionDefinition","constructor","_defineProperty","headline","content","items","entityId","entityType","entityUrl","linkText","markdown","expected","image","parse","parts","context","entityRefs","length","reportDiagnostic","SECTION_REJECTED","map","e","undefined","description","imageUrl","markdownImageUrl","callout","h1Index","findIndex","p","_p$meta","type","meta","level","blockquotes","leadingQuote","bq","_bq$meta","_bq$content","bqIndex","indexOf","raw","trim","text","badge"],"sources":["../../../../src/component/componentDefinitions/HeroEntitySectionDefinition.ts"],"sourcesContent":["import {\n headingText,\n headingMarkdown,\n bodyText,\n firstImageSrc,\n parseCalloutTag,\n extractEntityRefs,\n} from './parse-utils';\nimport type { Part } from '../../parts/parts';\nimport { getPartsByType } from '../../parts/parts';\nimport type {\n EntityItem,\n HeroEntityCompProps,\n HeroEntitySectionComponent,\n SectionFixture,\n} from '../types';\nimport type { SectionDefinition, ParseContext } from '../section-definition';\nimport { DIAGNOSTIC_TYPES } from '../diagnosticTypes';\n\n/**\n * Hero section whose body carries one or more web5 entity links\n * (`web5://entity/...`). It mirrors the framing of {@link HeroSectionDefinition}\n * (leading callout, h1, image, intro text) but, instead of turning the links\n * into CTA buttons, surfaces them as resolvable entities.\n *\n * Registered ahead of `HeroSectionDefinition` so an entity-bearing hero is\n * claimed here first. The pattern requires at least one `link[entityType]`, and\n * `parse()` returns `null` when no entity refs are found, letting a plain hero\n * fall through to `HeroSectionDefinition`.\n */\nexport class HeroEntitySectionDefinition\n implements SectionDefinition<HeroEntitySectionComponent>\n{\n sectionType = 'hero-entity' as const;\n singleInstance = true;\n patterns = [\n // Leading slots mirror HeroSectionDefinition: section descriptor, then the\n // optional leading ECHO callout, then an optional h3 badge, then the h1.\n // Callout and badge are independent; the second `html_comment?` preserves the\n // legacy callout-before-descriptor ordering for pages saved before this change.\n // Entity links carried as list items (the canonical shape).\n 'html_comment? > callout? > html_comment? > h3? > h1 > (image | t | link)* > list{item[1-]:link[entityType] > image? > (t | callout)*}',\n // Entity links sitting at the top level of the hero body.\n 'html_comment? > callout? > html_comment? > h3? > h1 > (image | t)* > (link[entityType] > image? > (t | callout)*)[1-] > list?',\n ];\n\n defaults: HeroEntityCompProps = {\n headline: 'Featured collection',\n content: 'Explore the highlights from this collection.',\n items: [\n {\n entityId: 'example-1',\n entityType: 'product',\n entityUrl: 'web5://entity/product/example-1',\n linkText: 'Example product',\n },\n ],\n };\n\n fixtures: SectionFixture<HeroEntityCompProps>[] = [\n {\n markdown: `<!-- section:hero, id:hero, semantic:hero -->\n\n# **Nike** collections\n\n![Nike Life collection](web5://image/Nike+Life+collection)\nNike offerings span performance and lifestyle lines.\n\n* [Nike Throwback Future Collection](web5://entity/article/WAyuiC63OSjwpvnOyZcblQ)\n* [Nike Life Cable Knit Turtleneck Sweater](web5://entity/product/ZDvHxsa3IJA8CyGX8BNpdw)\n* [Nike x Jacquemus NRG HE Swoosh Hoodie](web5://entity/product/qH2lqINGneHWYVZzzGnXTA)`,\n expected: {\n headline: '**Nike** collections',\n content: 'Nike offerings span performance and lifestyle lines.',\n image: 'web5://image/Nike+Life+collection',\n items: [\n {\n entityId: 'WAyuiC63OSjwpvnOyZcblQ',\n entityType: 'article',\n entityUrl:\n 'web5://entity/article/WAyuiC63OSjwpvnOyZcblQ',\n linkText: 'Nike Throwback Future Collection',\n },\n {\n entityId: 'ZDvHxsa3IJA8CyGX8BNpdw',\n entityType: 'product',\n entityUrl:\n 'web5://entity/product/ZDvHxsa3IJA8CyGX8BNpdw',\n linkText: 'Nike Life Cable Knit Turtleneck Sweater',\n },\n {\n entityId: 'qH2lqINGneHWYVZzzGnXTA',\n entityType: 'product',\n entityUrl:\n 'web5://entity/product/qH2lqINGneHWYVZzzGnXTA',\n linkText: 'Nike x Jacquemus NRG HE Swoosh Hoodie',\n },\n ],\n },\n },\n ];\n\n parse(parts: Part[], context?: ParseContext): HeroEntityCompProps | null {\n const entityRefs = extractEntityRefs(parts);\n\n if (entityRefs.length === 0) {\n // Not an entity hero — let HeroSectionDefinition claim it.\n context?.reportDiagnostic?.(\n DIAGNOSTIC_TYPES.SECTION_REJECTED,\n 'hero-entity: no entity links found',\n );\n return null;\n }\n\n const items: EntityItem[] = entityRefs.map((e) => ({\n entityId: e.entityId,\n entityType: e.entityType,\n entityUrl: e.entityUrl,\n linkText: e.linkText || undefined,\n description: e.description || undefined,\n imageUrl: e.markdownImageUrl,\n callout: e.callout,\n }));\n\n // The leading callout before the h1 — the ECHO the orchestrator emits as the\n // hero's opening line, right after the section descriptor — is part of the\n // hero, mirroring HeroSectionDefinition. A trailing callout falls through to a\n // downstream callout section. (Per-item callouts inside the entity list are\n // handled by extractEntityRefs above and are unaffected.)\n const h1Index = parts.findIndex(\n (p) =>\n p.type === 'heading' && (p.meta as { level?: number })?.level === 1,\n );\n const blockquotes = getPartsByType<Part>(parts, 'callout');\n let leadingQuote: string | undefined;\n\n for (const bq of blockquotes) {\n const bqIndex = parts.indexOf(bq);\n if (h1Index >= 0 && bqIndex > h1Index) {\n continue;\n }\n const raw = (bq.meta?.markdown as string)?.trim() || bq.content?.trim();\n if (!raw) {\n continue;\n }\n leadingQuote = parseCalloutTag(raw).text || undefined;\n }\n\n return {\n // Markdown-preserving (not headingText): the hero title now carries the\n // bold+italic hook the ECHO callout used to own, so the FE needs the\n // formatting, not just the plain text. See FeatureHeroEntity.tsx.\n headline: headingMarkdown(parts, 1),\n badge: headingText(parts, 3) || undefined,\n content: bodyText(parts) || undefined,\n image: firstImageSrc(parts),\n leadingQuote,\n items,\n };\n }\n}\n"],"mappings":"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","ignoreList":[]}