@cosmicdrift/kumiko-headless 0.327.0 → 0.328.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/dist/apex/css.d.ts +3 -0
  2. package/{src/apex/css.ts → dist/apex/css.js} +11 -24
  3. package/dist/apex/index.d.ts +229 -0
  4. package/dist/apex/index.js +360 -0
  5. package/dist/apex/lightbox.d.ts +4 -0
  6. package/{src/apex/lightbox.ts → dist/apex/lightbox.js} +0 -4
  7. package/dist/contracts/asset.d.ts +15 -0
  8. package/dist/contracts/asset.js +17 -0
  9. package/dist/contracts/index.d.ts +3 -0
  10. package/dist/contracts/index.js +1 -0
  11. package/dist/contracts/locale.d.ts +7 -0
  12. package/dist/contracts/locale.js +11 -0
  13. package/dist/contracts/primitives.d.ts +93 -0
  14. package/dist/contracts/primitives.js +21 -0
  15. package/dist/dispatcher/index.d.ts +2 -0
  16. package/dist/dispatcher/index.js +1 -0
  17. package/dist/dispatcher/types.d.ts +86 -0
  18. package/dist/dispatcher/types.js +34 -0
  19. package/dist/form/form-controller.d.ts +2 -0
  20. package/dist/form/form-controller.js +404 -0
  21. package/dist/form/index.d.ts +3 -0
  22. package/dist/form/index.js +2 -0
  23. package/dist/form/types.d.ts +71 -0
  24. package/dist/form/types.js +1 -0
  25. package/dist/form/zod-bridge.d.ts +5 -0
  26. package/{src/form/zod-bridge.ts → dist/form/zod-bridge.js} +52 -58
  27. package/dist/format/escape.d.ts +5 -0
  28. package/{src/format/escape.ts → dist/format/escape.js} +43 -47
  29. package/dist/format/html-template.d.ts +9 -0
  30. package/dist/format/html-template.js +39 -0
  31. package/dist/format/index.d.ts +10 -0
  32. package/dist/format/index.js +192 -0
  33. package/dist/format/money.d.ts +1 -0
  34. package/{src/format/money.ts → dist/format/money.js} +6 -5
  35. package/dist/index.d.ts +14 -0
  36. package/dist/index.js +9 -0
  37. package/dist/locale-routing/index.d.ts +39 -0
  38. package/dist/locale-routing/index.js +143 -0
  39. package/dist/nav/index.d.ts +2 -0
  40. package/dist/nav/index.js +1 -0
  41. package/dist/nav/resolve.d.ts +2 -0
  42. package/dist/nav/resolve.js +58 -0
  43. package/dist/nav/types.d.ts +26 -0
  44. package/dist/nav/types.js +1 -0
  45. package/dist/store/create-store.d.ts +2 -0
  46. package/{src/store/create-store.ts → dist/store/create-store.js} +21 -23
  47. package/dist/store/equality.d.ts +1 -0
  48. package/{src/store/equality.ts → dist/store/equality.js} +19 -17
  49. package/dist/store/index.d.ts +3 -0
  50. package/dist/store/index.js +2 -0
  51. package/dist/store/types.d.ts +7 -0
  52. package/dist/store/types.js +10 -0
  53. package/dist/view-model/edit.d.ts +10 -0
  54. package/dist/view-model/edit.js +392 -0
  55. package/dist/view-model/embedded-list.d.ts +27 -0
  56. package/dist/view-model/embedded-list.js +63 -0
  57. package/dist/view-model/index.d.ts +7 -0
  58. package/dist/view-model/index.js +3 -0
  59. package/dist/view-model/list.d.ts +16 -0
  60. package/dist/view-model/list.js +201 -0
  61. package/dist/view-model/types.d.ts +300 -0
  62. package/dist/view-model/types.js +1 -0
  63. package/package.json +31 -12
  64. package/src/changes.json +7 -0
  65. package/src/apex/__tests__/lightbox.test.ts +0 -20
  66. package/src/apex/__tests__/page-head.test.ts +0 -63
  67. package/src/apex/__tests__/render.test.ts +0 -401
  68. package/src/apex/index.ts +0 -667
  69. package/src/contracts/__tests__/contracts.test.ts +0 -123
  70. package/src/contracts/__tests__/field-issue-compat.test.ts +0 -19
  71. package/src/contracts/asset.ts +0 -48
  72. package/src/contracts/index.ts +0 -23
  73. package/src/contracts/locale.ts +0 -33
  74. package/src/contracts/primitives.ts +0 -158
  75. package/src/dispatcher/__tests__/contract.test.ts +0 -163
  76. package/src/dispatcher/index.ts +0 -16
  77. package/src/dispatcher/types.ts +0 -217
  78. package/src/form/__tests__/conditional-fields.test.ts +0 -177
  79. package/src/form/__tests__/form-controller.test.ts +0 -231
  80. package/src/form/__tests__/submit.test.ts +0 -575
  81. package/src/form/__tests__/validation.test.ts +0 -274
  82. package/src/form/form-controller.ts +0 -458
  83. package/src/form/index.ts +0 -15
  84. package/src/form/types.ts +0 -297
  85. package/src/format/__tests__/escape.test.ts +0 -79
  86. package/src/format/__tests__/format.test.ts +0 -281
  87. package/src/format/__tests__/html-template.test.ts +0 -47
  88. package/src/format/html-template.ts +0 -49
  89. package/src/format/index.ts +0 -219
  90. package/src/index.ts +0 -118
  91. package/src/locale-routing/__tests__/locale-routing.test.ts +0 -276
  92. package/src/locale-routing/index.ts +0 -218
  93. package/src/nav/__tests__/resolve.test.ts +0 -279
  94. package/src/nav/index.ts +0 -8
  95. package/src/nav/resolve.ts +0 -60
  96. package/src/nav/types.ts +0 -70
  97. package/src/store/__tests__/create-store.test.ts +0 -139
  98. package/src/store/__tests__/equality.test.ts +0 -66
  99. package/src/store/index.ts +0 -3
  100. package/src/store/types.ts +0 -27
  101. package/src/view-model/__tests__/edit.test.ts +0 -1072
  102. package/src/view-model/__tests__/embedded-list.test.ts +0 -96
  103. package/src/view-model/__tests__/field-labels.test.ts +0 -62
  104. package/src/view-model/__tests__/list.test.ts +0 -428
  105. package/src/view-model/edit.ts +0 -641
  106. package/src/view-model/embedded-list.ts +0 -89
  107. package/src/view-model/index.ts +0 -43
  108. package/src/view-model/list.ts +0 -277
  109. package/src/view-model/types.ts +0 -392
@@ -0,0 +1,3 @@
1
+ export declare const APEX_NAV_MENU_CSS = "\n .nav-toggle { display: none; }\n .nav-toggle__trigger { display: none; align-items: center; justify-content: center;\n width: 2.25rem; height: 2.25rem; border-radius: 0.5rem; cursor: pointer;\n color: var(--fg-muted); list-style: none; }\n .nav-toggle__trigger::-webkit-details-marker { display: none; }\n .nav-toggle__trigger:hover { color: var(--fg); background: var(--bg-muted); }\n .nav-toggle__trigger svg { width: 1.35rem; height: 1.35rem; }\n .apex-dark .nav-toggle__trigger { color: var(--on-dark-muted); }\n .apex-dark .nav-toggle__trigger:hover { color: var(--on-dark); background: rgba(255,255,255,0.08); }\n\n .nav-menu { position: relative; display: inline-flex; }\n .nav-menu__trigger { display: inline-flex; align-items: center; gap: 0.3rem;\n font: inherit; font-size: 0.9375rem; color: var(--fg-muted);\n cursor: pointer; list-style: none; }\n .nav-menu__trigger::-webkit-details-marker { display: none; }\n .nav-menu__trigger:hover { color: var(--fg); }\n .nav-menu__chev { display: inline-flex; transition: transform 0.15s; opacity: 0.7; }\n .nav-menu__chev svg { width: 0.95em; height: 0.95em; }\n .nav-menu[open] .nav-menu__chev { transform: rotate(180deg); }\n .nav-menu__panel { position: absolute; top: calc(100% + 0.5rem); left: 0; z-index: 20;\n min-width: 21rem; padding: 0.5rem; background: var(--bg-card); color: var(--fg);\n border: 1px solid var(--border); border-radius: 0.75rem; box-shadow: var(--shadow);\n flex-direction: column; gap: 0.125rem; }\n .nav-menu[open] .nav-menu__panel { display: flex; }\n .nav-menu__item { display: flex; gap: 0.75rem; align-items: flex-start;\n padding: 0.6rem 0.7rem; border-radius: 0.5rem; color: var(--fg); }\n .nav-menu__item:hover { background: var(--bg-muted); color: var(--fg); }\n .nav-menu__icon { flex: none; display: inline-flex; align-items: center; justify-content: center;\n width: 2rem; height: 2rem; border-radius: 0.5rem;\n background: color-mix(in srgb, var(--primary) 12%, transparent); color: var(--primary); }\n .nav-menu__icon svg { width: 1.1rem; height: 1.1rem; }\n .nav-menu__text { display: flex; flex-direction: column; gap: 0.1rem; }\n .nav-menu__title { font-weight: 600; font-size: 0.9rem; line-height: 1.2; }\n .nav-menu__desc { font-size: 0.8rem; color: var(--fg-muted); line-height: 1.35; }\n .nav-menu__sep { height: 1px; background: var(--border); margin: 0.375rem 0.3rem; }\n .nav-menu__more { display: inline-flex; padding: 0.4rem 0.7rem; font-size: 0.85rem; font-weight: 600; color: var(--primary); }\n .nav-menu__more:hover { color: var(--primary-hover); }\n .apex-dark .nav-menu__trigger { color: var(--on-dark-muted); }\n .apex-dark .nav-menu__trigger:hover { color: var(--on-dark); }\n /* The panel is a light popover; its links must NOT inherit the dark-chrome\n nav-link color (.apex-dark .nav-links a, specificity 0,2,1) or a consumer's\n own .nav-links a. Pin both link types with matching specificity (0,2,1) so\n titles + the footer link stay readable in the dark theme. */\n .nav-menu__panel a.nav-menu__item { color: var(--fg); }\n .nav-menu__panel a.nav-menu__item:hover { color: var(--fg); }\n .nav-menu__panel a.nav-menu__more { color: var(--primary); }\n .nav-menu__panel a.nav-menu__more:hover { color: var(--primary-hover); }\n";
2
+ export declare const APEX_NAV_TOGGLE_RESPONSIVE_CSS = "\n @media (max-width: 640px) {\n .nav { position: relative; }\n .nav .nav-toggle { display: inline-flex; }\n .nav .nav-toggle__trigger { display: inline-flex; }\n .nav-toggle[open] > .nav-links { display: flex; flex-direction: column; align-items: stretch;\n gap: 0.25rem; position: absolute; top: calc(100% + 0.5rem); left: 0; right: 0; z-index: 30;\n background: var(--bg-card); border: 1px solid var(--border); border-radius: 0.75rem;\n box-shadow: var(--shadow); padding: 0.75rem; }\n .nav-toggle[open] > .nav-links > a { color: var(--fg); padding: 0.4rem 0.25rem; }\n .nav-toggle[open] > .nav-links > a:hover { color: var(--fg); }\n .nav-toggle .nav-menu { display: block; }\n .nav-toggle .nav-menu__panel { position: static; min-width: 0;\n margin-top: 0.375rem; box-shadow: none; }\n }\n";
3
+ export declare const APEX_STRUCTURAL_CSS: string;
@@ -14,7 +14,6 @@
14
14
  // --accent-hover unset → eyebrow falls back to --primary
15
15
  // --hero-tagline-max unset → hero tagline runs full column (var(--hero-tagline-max, none))
16
16
  // icon tint → color-mix derives a faint --primary wash, no hardcoded rgba
17
-
18
17
  const BASE_LAYOUT = `
19
18
  * { box-sizing: border-box; }
20
19
  html, body { margin: 0; padding: 0; }
@@ -48,7 +47,6 @@ const BASE_LAYOUT = `
48
47
  .eyebrow { display: inline-block; font-size: 0.8125rem; font-weight: 600; letter-spacing: 0.04em;
49
48
  text-transform: uppercase; color: var(--accent-hover, var(--primary)); margin-bottom: 0.75rem; }
50
49
  `;
51
-
52
50
  const HERO = `
53
51
  .hero { padding: 4.5rem 0 4rem; }
54
52
  .hero-grid { display: grid; grid-template-columns: 1.05fr 1fr; gap: 3.5rem; align-items: center; }
@@ -74,7 +72,6 @@ const HERO = `
74
72
  .apex-lightbox__close:hover { background: var(--bg-muted); }
75
73
  .apex-dark .apex-lightbox__close { background: var(--on-dark); color: var(--primary); border-color: var(--on-dark-border); }
76
74
  `;
77
-
78
75
  const FEATURES = `
79
76
  .features { background: var(--bg-muted); }
80
77
  .feature-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem; }
@@ -85,7 +82,6 @@ const FEATURES = `
85
82
  .feature h3 { font-size: 1.0625rem; margin: 0 0 0.4rem; }
86
83
  .feature p { color: var(--fg-muted); margin: 0; font-size: 0.9375rem; }
87
84
  `;
88
-
89
85
  const PRICING = `
90
86
  .price-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.25rem; align-items: stretch; }
91
87
  .price-card { position: relative; background: var(--bg-card); border: 1px solid var(--border);
@@ -105,20 +101,17 @@ const PRICING = `
105
101
  .price-list li.price-cap { font-weight: 600; color: var(--fg); }
106
102
  .price-card .btn { text-align: center; margin-top: 0.5rem; }
107
103
  `;
108
-
109
104
  const TRUST = `
110
105
  .trust-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 2rem; }
111
106
  .trust-item h3 { font-size: 1.0625rem; margin: 0 0 0.4rem; }
112
107
  .trust-item p { color: var(--fg-muted); margin: 0; font-size: 0.9375rem; }
113
108
  `;
114
-
115
109
  const FINAL_CTA = `
116
110
  .final-cta { text-align: center; }
117
111
  .final-cta-pony { width: 96px; height: auto; display: block; margin: 0 auto 1rem; }
118
112
  .final-cta h2 { font-size: clamp(1.6rem, 3.5vw, 2.25rem); margin: 0 0 0.75rem; }
119
113
  .final-cta p { color: var(--fg-muted); margin: 0 0 1.75rem; font-size: 1.0625rem; }
120
114
  `;
121
-
122
115
  // Light chrome (default). Header floats translucent over the page; footer is a
123
116
  // muted band. Dark theme overrides both below.
124
117
  const CHROME_LIGHT = `
@@ -150,7 +143,6 @@ const CHROME_LIGHT = `
150
143
  .footer-bottom { margin-top: 2.5rem; padding-top: 1.5rem; border-top: 1px solid var(--border);
151
144
  display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0.75rem; }
152
145
  `;
153
-
154
146
  // Dark sandwich: header/hero/final-cta/footer ride on --primary. Buttons invert
155
147
  // (a --primary button on --primary would vanish) → light button, --primary text.
156
148
  const CHROME_DARK = `
@@ -185,7 +177,6 @@ const CHROME_DARK = `
185
177
  .apex-dark .footer-col a:hover { color: var(--on-dark); }
186
178
  .apex-dark .footer-bottom { border-top: 1px solid var(--on-dark-border); }
187
179
  `;
188
-
189
180
  const RESPONSIVE = `
190
181
  @media (max-width: 900px) {
191
182
  .hero-grid { grid-template-columns: 1fr; gap: 2.5rem; }
@@ -198,7 +189,6 @@ const RESPONSIVE = `
198
189
  .feature-grid, .price-grid, .trust-grid, .footer-grid { grid-template-columns: 1fr; }
199
190
  }
200
191
  `;
201
-
202
192
  // header renders navLinks TWICE (renderApexHeader): a plain always-visible
203
193
  // .nav-links row, plus a copy nested in .nav-toggle for < 640px — a closed
204
194
  // <details> can't be forced "always open" on desktop via CSS (Chrome keeps
@@ -256,7 +246,6 @@ export const APEX_NAV_MENU_CSS = `
256
246
  .nav-menu__panel a.nav-menu__more { color: var(--primary); }
257
247
  .nav-menu__panel a.nav-menu__more:hover { color: var(--primary-hover); }
258
248
  `;
259
-
260
249
  // Standalone mobile-open rules for .nav-toggle, split out of RESPONSIVE so a
261
250
  // consumer calling renderApexHeader() outside renderApexPage (money-horse,
262
251
  // show-pony, the docs sample) can include just this alongside
@@ -288,16 +277,14 @@ export const APEX_NAV_TOGGLE_RESPONSIVE_CSS = `
288
277
  margin-top: 0.375rem; box-shadow: none; }
289
278
  }
290
279
  `;
291
-
292
- export const APEX_STRUCTURAL_CSS =
293
- BASE_LAYOUT +
294
- HERO +
295
- FEATURES +
296
- PRICING +
297
- TRUST +
298
- FINAL_CTA +
299
- CHROME_LIGHT +
300
- CHROME_DARK +
301
- APEX_NAV_MENU_CSS +
302
- APEX_NAV_TOGGLE_RESPONSIVE_CSS +
303
- RESPONSIVE;
280
+ export const APEX_STRUCTURAL_CSS = BASE_LAYOUT +
281
+ HERO +
282
+ FEATURES +
283
+ PRICING +
284
+ TRUST +
285
+ FINAL_CTA +
286
+ CHROME_LIGHT +
287
+ CHROME_DARK +
288
+ APEX_NAV_MENU_CSS +
289
+ APEX_NAV_TOGGLE_RESPONSIVE_CSS +
290
+ RESPONSIVE;
@@ -0,0 +1,229 @@
1
+ import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
2
+ export { APEX_NAV_MENU_CSS, APEX_NAV_TOGGLE_RESPONSIVE_CSS, APEX_STRUCTURAL_CSS } from "./css.js";
3
+ export { APEX_LIGHTBOX_HTML, APEX_LIGHTBOX_SCRIPT, APEX_LIGHTBOX_SCRIPT_CSP_HASH, } from "./lightbox.js";
4
+ export type ApexTheme = "light" | "dark";
5
+ /** Brand is raw CSS the app owns: the :root token block and optional @font-face.
6
+ * Token names referenced by the structural CSS: --bg --bg-card --bg-muted
7
+ * --border --fg --fg-muted --fg-subtle --primary --primary-hover --primary-fg
8
+ * --status-ok --shadow. Optional: --accent --accent-fg --font-mono --font-body,
9
+ * and (dark theme) --on-dark --on-dark-muted --on-dark-border. */
10
+ export type ApexBrand = {
11
+ readonly tokensCss: string;
12
+ readonly fontFaceCss?: string;
13
+ };
14
+ export type ApexCtaVariant = "primary" | "secondary" | "link";
15
+ export type ApexCta = {
16
+ readonly label: string;
17
+ /** App-authored only, no user input — escapeHtml encodes entities but does
18
+ * NOT sanitize the URL scheme (a `javascript:`/`data:` URI survives it
19
+ * unchanged). Trust boundary is the deploy-time apex config, not runtime. */
20
+ readonly href: string;
21
+ /** "link" renders a plain anchor (no .btn). Default "primary". */
22
+ readonly variant?: ApexCtaVariant;
23
+ };
24
+ export type ApexLink = {
25
+ readonly kind?: "link";
26
+ readonly label: string;
27
+ readonly href: string;
28
+ };
29
+ /** One entry inside a dropdown nav menu: icon + title + optional description. */
30
+ export type ApexNavMenuItem = {
31
+ /** Inner SVG markup (paths), wrapped by the standard 24px icon <svg>.
32
+ * ponytail: rendered verbatim, no sanitizer — trusted because it's
33
+ * app-authored at deploy time, same trust boundary as ApexCta.href. If an
34
+ * app ever sources this from tenant/user content, sanitize at that
35
+ * construction boundary before it reaches here. */
36
+ readonly icon?: string;
37
+ readonly title: string;
38
+ readonly desc?: string;
39
+ readonly href: string;
40
+ };
41
+ /** A header nav entry that opens a dropdown (icon/title/desc rows) instead of
42
+ * navigating. CSS-only: reveals on hover + keyboard focus. */
43
+ export type ApexNavMenu = {
44
+ readonly kind: "menu";
45
+ readonly label: string;
46
+ readonly items: readonly ApexNavMenuItem[];
47
+ /** Optional link under a divider at the foot of the panel (e.g. "See all →"). */
48
+ readonly footer?: ApexLink;
49
+ };
50
+ /** A header nav entry: a plain link, or a dropdown menu. */
51
+ export type ApexNavEntry = ApexLink | ApexNavMenu;
52
+ export type ApexImage = {
53
+ readonly src: string;
54
+ readonly alt: string;
55
+ readonly width?: number;
56
+ readonly height?: number;
57
+ };
58
+ export type ApexHeader = {
59
+ readonly brand: {
60
+ readonly href: string;
61
+ readonly label: string;
62
+ readonly logoSrc?: string;
63
+ };
64
+ /** Plain links and/or dropdown menus, in order. */
65
+ readonly navLinks?: readonly ApexNavEntry[];
66
+ readonly actions?: readonly ApexCta[];
67
+ /** aria-label for the mobile hamburger trigger (< 640px). Default "Menu". */
68
+ readonly menuLabel?: string;
69
+ };
70
+ export type ApexFooterColumn = {
71
+ readonly heading: string;
72
+ readonly links: readonly ApexLink[];
73
+ };
74
+ export type ApexFooter = {
75
+ readonly brand: {
76
+ readonly label: string;
77
+ readonly logoSrc?: string;
78
+ };
79
+ readonly tagline?: string;
80
+ readonly columns?: readonly ApexFooterColumn[];
81
+ /** Plain text — use unicode (© ·), not HTML entities; rendered escaped. */
82
+ readonly bottomLeft?: string;
83
+ readonly bottomRight?: string;
84
+ };
85
+ export type ApexHeroSection = {
86
+ readonly kind: "hero";
87
+ readonly logo?: ApexImage;
88
+ readonly title: string;
89
+ readonly tagline: string;
90
+ readonly ctas?: readonly ApexCta[];
91
+ /** App-authored trusted HTML (e.g. <strong>…<br/>); rendered verbatim. */
92
+ readonly metaHtml?: string;
93
+ readonly screenshot?: ApexImage;
94
+ };
95
+ export type ApexFeature = {
96
+ /** Inner SVG markup (paths), wrapped by the standard 24px icon <svg>.
97
+ * ponytail: rendered verbatim, no sanitizer — trusted because it's
98
+ * app-authored at deploy time, same trust boundary as ApexCta.href. If an
99
+ * app ever sources this from tenant/user content, sanitize at that
100
+ * construction boundary before it reaches here. */
101
+ readonly icon?: string;
102
+ readonly title: string;
103
+ readonly desc: string;
104
+ };
105
+ export type ApexFeatureGridSection = {
106
+ readonly kind: "feature-grid";
107
+ readonly id?: string;
108
+ readonly eyebrow?: string;
109
+ readonly heading: string;
110
+ readonly sub?: string;
111
+ /** Sit on the muted band (.features). Default true. */
112
+ readonly muted?: boolean;
113
+ readonly items: readonly ApexFeature[];
114
+ };
115
+ export type ApexPricingTier = {
116
+ readonly name: string;
117
+ readonly tagline?: string;
118
+ /** App-formatted ("4,99 €", "0 €", "auf Anfrage"). */
119
+ readonly amount: string;
120
+ /** Localized suffix after the amount ("/Monat", "/month"). */
121
+ readonly priceSuffix?: string;
122
+ readonly featured?: boolean;
123
+ readonly badge?: string;
124
+ readonly capLine?: string;
125
+ readonly benefits: readonly string[];
126
+ readonly cta: ApexCta;
127
+ };
128
+ export type ApexPricingGridSection = {
129
+ readonly kind: "pricing-grid";
130
+ readonly id?: string;
131
+ readonly eyebrow?: string;
132
+ readonly heading: string;
133
+ readonly sub?: string;
134
+ readonly tiers: readonly ApexPricingTier[];
135
+ };
136
+ export type ApexInfoItem = {
137
+ readonly title: string;
138
+ readonly desc: string;
139
+ };
140
+ export type ApexInfoGridSection = {
141
+ readonly kind: "info-grid";
142
+ readonly id?: string;
143
+ readonly eyebrow?: string;
144
+ readonly heading?: string;
145
+ readonly sub?: string;
146
+ readonly muted?: boolean;
147
+ readonly items: readonly ApexInfoItem[];
148
+ };
149
+ export type ApexFinalCtaSection = {
150
+ readonly kind: "final-cta";
151
+ readonly image?: ApexImage;
152
+ readonly heading: string;
153
+ readonly sub?: string;
154
+ readonly cta: ApexCta;
155
+ };
156
+ /** Escape hatch for an app-specific section: raw, app-authored HTML. */
157
+ export type ApexHtmlSection = {
158
+ readonly kind: "html";
159
+ readonly html: string;
160
+ };
161
+ export type ApexSection = ApexHeroSection | ApexFeatureGridSection | ApexPricingGridSection | ApexInfoGridSection | ApexFinalCtaSection | ApexHtmlSection;
162
+ export type ApexHead = {
163
+ readonly lang: string;
164
+ readonly title: string;
165
+ readonly description: string;
166
+ readonly canonicalUrl?: string;
167
+ readonly faviconHref?: string;
168
+ readonly ogImage?: string;
169
+ /** hreflang alternates for multilingual SEO (e.g. the other language's URL). */
170
+ readonly alternates?: readonly {
171
+ readonly hreflang: string;
172
+ readonly href: string;
173
+ }[];
174
+ /** Robots meta content (e.g. "index, follow", "noindex, nofollow"). Omit = no tag. */
175
+ readonly robots?: string;
176
+ /** og:site_name — brand name for social shares. */
177
+ readonly siteName?: string;
178
+ /** og:locale — e.g. "de_DE", "en_US". */
179
+ readonly locale?: string;
180
+ /** twitter:site — @handle for X/Twitter card. */
181
+ readonly twitterSite?: string;
182
+ /** URLs for <link rel="preconnect"> hints (Core Web Vitals). */
183
+ readonly preconnects?: readonly string[];
184
+ /** Arbitrary JSON-LD structured data (Schema.org). Rendered as-is into <script type="application/ld+json">. */
185
+ readonly schemaJson?: Record<string, unknown>;
186
+ };
187
+ export type ApexPage = {
188
+ readonly theme?: ApexTheme;
189
+ readonly brand: ApexBrand;
190
+ readonly head: ApexHead;
191
+ readonly header: ApexHeader;
192
+ readonly sections: readonly ApexSection[];
193
+ readonly footer: ApexFooter;
194
+ };
195
+ /** Render just the apex header chrome (brand + nav + actions). Exported so a
196
+ * consumer that composes its own page shell (not a full apex page) can reuse
197
+ * the identical header — markup stays single-source. */
198
+ export declare function renderApexHeader(h: ApexHeader): string;
199
+ export declare function renderApexHeadTags(head: ApexHead): string;
200
+ export declare function renderApexPage(page: ApexPage): string;
201
+ /** Per-request head metadata (Open-Graph/title/description) for a
202
+ * server-rendered HTML shell. Only `title` is required; everything else
203
+ * falls back to no tag rather than a placeholder. */
204
+ export type PageHeadMeta = {
205
+ readonly title: string;
206
+ readonly description?: string;
207
+ readonly ogImage?: string;
208
+ readonly canonicalUrl?: string;
209
+ readonly siteName?: string;
210
+ readonly locale?: string;
211
+ };
212
+ export type PageHeadSystemQuery = (type: string, payload: unknown, tenantId: TenantId) => Promise<unknown>;
213
+ export type PageHeadResolver = (input: {
214
+ readonly path: string;
215
+ readonly host: string;
216
+ readonly systemQuery: PageHeadSystemQuery;
217
+ }) => Promise<PageHeadMeta | null>;
218
+ export declare function injectPageHead(html: string, tagsHtml: string): string;
219
+ /** Resolves per-request head metadata and splices it into `html`, capped at
220
+ * the shared timeout. Never throws and never changes `html` on a resolver
221
+ * error, a `null` resolve, or a timeout — the caller's shell always ships
222
+ * unchanged. runProdApp and runDevApp both call this single function, so a
223
+ * future change to timeout or fallback semantics has one place to land
224
+ * (kumiko-framework#3026). */
225
+ export declare function resolveAndInjectPageHead(html: string, resolvePageHead: PageHeadResolver, input: {
226
+ readonly path: string;
227
+ readonly host: string;
228
+ readonly systemQuery: PageHeadSystemQuery;
229
+ }): Promise<string>;