@flamingo-stack/openframe-frontend-core 0.0.315 → 0.0.316

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 (163) hide show
  1. package/dist/{chunk-7U4YFQX2.js → chunk-2Y4DLBFO.js} +96 -92
  2. package/dist/{chunk-7U4YFQX2.js.map → chunk-2Y4DLBFO.js.map} +1 -1
  3. package/dist/{chunk-HATCNFQL.cjs → chunk-4MCMPYEM.cjs} +12 -12
  4. package/dist/{chunk-HATCNFQL.cjs.map → chunk-4MCMPYEM.cjs.map} +1 -1
  5. package/dist/{chunk-VY5YF4B7.js → chunk-4NVA6W3J.js} +27 -22
  6. package/dist/chunk-4NVA6W3J.js.map +1 -0
  7. package/dist/chunk-4V3TCOFC.cjs +394 -0
  8. package/dist/chunk-4V3TCOFC.cjs.map +1 -0
  9. package/dist/{chunk-A2H6TFS4.cjs → chunk-63A53WQN.cjs} +33 -33
  10. package/dist/{chunk-A2H6TFS4.cjs.map → chunk-63A53WQN.cjs.map} +1 -1
  11. package/dist/{chunk-6W54MBU2.js → chunk-64DZ2J7Q.js} +5 -5
  12. package/dist/{chunk-47JZOP7Y.js → chunk-6KERXOFE.js} +3 -3
  13. package/dist/{chunk-JALO4TAZ.js → chunk-AI5X5JTD.js} +4 -4
  14. package/dist/chunk-CSLMCBZV.js +1464 -0
  15. package/dist/chunk-CSLMCBZV.js.map +1 -0
  16. package/dist/{chunk-BSAFGQVW.cjs → chunk-CUNMBP3A.cjs} +13 -13
  17. package/dist/{chunk-BSAFGQVW.cjs.map → chunk-CUNMBP3A.cjs.map} +1 -1
  18. package/dist/{chunk-TVNILN2F.cjs → chunk-DHVL36CA.cjs} +40 -40
  19. package/dist/{chunk-TVNILN2F.cjs.map → chunk-DHVL36CA.cjs.map} +1 -1
  20. package/dist/chunk-FCEVVNWY.cjs +1916 -0
  21. package/dist/chunk-FCEVVNWY.cjs.map +1 -0
  22. package/dist/chunk-FOVX3W3C.cjs +1464 -0
  23. package/dist/chunk-FOVX3W3C.cjs.map +1 -0
  24. package/dist/{chunk-4D37W55K.js → chunk-GHVVOST5.js} +95 -116
  25. package/dist/chunk-GHVVOST5.js.map +1 -0
  26. package/dist/{chunk-TRSDXD23.js → chunk-JAZM3A7E.js} +2 -2
  27. package/dist/{chunk-TK6OABYF.js → chunk-JEBL5PQK.js} +21 -35
  28. package/dist/{chunk-TK6OABYF.js.map → chunk-JEBL5PQK.js.map} +1 -1
  29. package/dist/{chunk-5ATH263N.cjs → chunk-L5JSGNT3.cjs} +35 -35
  30. package/dist/{chunk-5ATH263N.cjs.map → chunk-L5JSGNT3.cjs.map} +1 -1
  31. package/dist/{chunk-TQ7CMFSY.cjs → chunk-LAMDFGE3.cjs} +41 -36
  32. package/dist/chunk-LAMDFGE3.cjs.map +1 -0
  33. package/dist/{chunk-V4IIBNTA.js → chunk-LQHMXPOJ.js} +5 -5
  34. package/dist/{chunk-LGLPNWS6.cjs → chunk-LWNPMLIH.cjs} +3 -3
  35. package/dist/{chunk-LGLPNWS6.cjs.map → chunk-LWNPMLIH.cjs.map} +1 -1
  36. package/dist/chunk-M3NULYCR.js +1916 -0
  37. package/dist/chunk-M3NULYCR.js.map +1 -0
  38. package/dist/{chunk-MOOV4ORG.js → chunk-OKGZK6TT.js} +3 -3
  39. package/dist/{chunk-WFHNXCI3.cjs → chunk-OLEW7FYZ.cjs} +123 -144
  40. package/dist/chunk-OLEW7FYZ.cjs.map +1 -0
  41. package/dist/chunk-PIJ4JLJU.js +394 -0
  42. package/dist/chunk-PIJ4JLJU.js.map +1 -0
  43. package/dist/{chunk-E4CQ4RUG.js → chunk-Q4AMYLKX.js} +11 -11
  44. package/dist/{chunk-FQOTC3UU.cjs → chunk-QJGRP2YE.cjs} +4 -4
  45. package/dist/{chunk-FQOTC3UU.cjs.map → chunk-QJGRP2YE.cjs.map} +1 -1
  46. package/dist/{chunk-ZPK5HW7B.cjs → chunk-UGDGUO26.cjs} +3 -3
  47. package/dist/{chunk-ZPK5HW7B.cjs.map → chunk-UGDGUO26.cjs.map} +1 -1
  48. package/dist/{chunk-QW6OL4NY.cjs → chunk-VCE3ZEN3.cjs} +5 -5
  49. package/dist/{chunk-QW6OL4NY.cjs.map → chunk-VCE3ZEN3.cjs.map} +1 -1
  50. package/dist/{chunk-2JPSWDSM.cjs → chunk-XAQJ4ZLY.cjs} +447 -443
  51. package/dist/{chunk-2JPSWDSM.cjs.map → chunk-XAQJ4ZLY.cjs.map} +1 -1
  52. package/dist/{chunk-2MLMZAK4.js → chunk-YFGDZFUG.js} +4 -4
  53. package/dist/{chunk-VFIWQGJZ.js → chunk-Z3YORGG4.js} +2 -2
  54. package/dist/{chunk-OSEKWT6X.cjs → chunk-ZYGVJXJ5.cjs} +33 -47
  55. package/dist/chunk-ZYGVJXJ5.cjs.map +1 -0
  56. package/dist/components/case-studies/index.cjs +18 -18
  57. package/dist/components/case-studies/index.cjs.map +1 -1
  58. package/dist/components/case-studies/index.js +8 -8
  59. package/dist/components/chat/index.cjs +8 -8
  60. package/dist/components/chat/index.js +7 -7
  61. package/dist/components/contact/index.cjs +9 -9
  62. package/dist/components/contact/index.js +8 -8
  63. package/dist/components/docs/doc-viewer.d.ts +4 -0
  64. package/dist/components/docs/doc-viewer.d.ts.map +1 -1
  65. package/dist/components/docs/index.cjs +11 -11
  66. package/dist/components/docs/index.js +10 -10
  67. package/dist/components/embeds/index.cjs +9 -9
  68. package/dist/components/embeds/index.js +8 -8
  69. package/dist/components/faq/faq-document-page.d.ts +18 -20
  70. package/dist/components/faq/faq-document-page.d.ts.map +1 -1
  71. package/dist/components/faq/index.cjs +10 -10
  72. package/dist/components/faq/index.js +9 -9
  73. package/dist/components/features/index.cjs +8 -8
  74. package/dist/components/features/index.js +7 -7
  75. package/dist/components/help-center-pages/delivery-page.d.ts +27 -0
  76. package/dist/components/help-center-pages/delivery-page.d.ts.map +1 -0
  77. package/dist/components/help-center-pages/index.cjs +164 -0
  78. package/dist/components/help-center-pages/index.cjs.map +1 -0
  79. package/dist/components/help-center-pages/index.d.ts +25 -0
  80. package/dist/components/help-center-pages/index.d.ts.map +1 -0
  81. package/dist/components/help-center-pages/index.js +164 -0
  82. package/dist/components/help-center-pages/index.js.map +1 -0
  83. package/dist/components/help-center-pages/onboarding-guides-catalog-page.d.ts +41 -0
  84. package/dist/components/help-center-pages/onboarding-guides-catalog-page.d.ts.map +1 -0
  85. package/dist/components/help-center-pages/product-releases-list-page.d.ts +34 -0
  86. package/dist/components/help-center-pages/product-releases-list-page.d.ts.map +1 -0
  87. package/dist/components/help-center-pages/roadmap-page.d.ts +40 -0
  88. package/dist/components/help-center-pages/roadmap-page.d.ts.map +1 -0
  89. package/dist/components/icons/index.cjs +3 -3
  90. package/dist/components/icons/index.js +2 -2
  91. package/dist/components/index.cjs +177 -1555
  92. package/dist/components/index.cjs.map +1 -1
  93. package/dist/components/index.js +348 -1726
  94. package/dist/components/index.js.map +1 -1
  95. package/dist/components/layout/page-layout.d.ts +4 -1
  96. package/dist/components/layout/page-layout.d.ts.map +1 -1
  97. package/dist/components/layout/title-block.d.ts +5 -1
  98. package/dist/components/layout/title-block.d.ts.map +1 -1
  99. package/dist/components/navigation/index.cjs +8 -8
  100. package/dist/components/navigation/index.js +7 -7
  101. package/dist/components/onboarding-guides/index.cjs +15 -364
  102. package/dist/components/onboarding-guides/index.cjs.map +1 -1
  103. package/dist/components/onboarding-guides/index.js +20 -369
  104. package/dist/components/onboarding-guides/index.js.map +1 -1
  105. package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts +9 -1
  106. package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
  107. package/dist/components/related-content/index.cjs +10 -10
  108. package/dist/components/related-content/index.js +9 -9
  109. package/dist/components/shared/dev-section/dev-section-page.d.ts +7 -1
  110. package/dist/components/shared/dev-section/dev-section-page.d.ts.map +1 -1
  111. package/dist/components/shared/dev-section/dev-section-view.d.ts +7 -1
  112. package/dist/components/shared/dev-section/dev-section-view.d.ts.map +1 -1
  113. package/dist/components/shared/legal-document/legal-document-page.d.ts +5 -1
  114. package/dist/components/shared/legal-document/legal-document-page.d.ts.map +1 -1
  115. package/dist/components/shared/product-release/release-detail-page.d.ts +11 -2
  116. package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
  117. package/dist/components/tickets/help-center-list.d.ts +5 -1
  118. package/dist/components/tickets/help-center-list.d.ts.map +1 -1
  119. package/dist/components/tickets/index.cjs +15 -1882
  120. package/dist/components/tickets/index.cjs.map +1 -1
  121. package/dist/components/tickets/index.js +28 -1895
  122. package/dist/components/tickets/index.js.map +1 -1
  123. package/dist/components/ui/file-manager/index.cjs +53 -53
  124. package/dist/components/ui/file-manager/index.cjs.map +1 -1
  125. package/dist/components/ui/file-manager/index.js +4 -4
  126. package/dist/components/ui/index.cjs +8 -8
  127. package/dist/components/ui/index.cjs.map +1 -1
  128. package/dist/components/ui/index.js +7 -7
  129. package/dist/hooks/index.cjs +5 -5
  130. package/dist/hooks/index.js +4 -4
  131. package/dist/index.cjs +10 -10
  132. package/dist/index.cjs.map +1 -1
  133. package/dist/index.js +9 -9
  134. package/package.json +7 -1
  135. package/src/components/docs/doc-viewer.tsx +21 -34
  136. package/src/components/faq/faq-document-page.tsx +33 -60
  137. package/src/components/help-center-pages/delivery-page.tsx +45 -0
  138. package/src/components/help-center-pages/index.ts +41 -0
  139. package/src/components/help-center-pages/onboarding-guides-catalog-page.tsx +66 -0
  140. package/src/components/help-center-pages/product-releases-list-page.tsx +58 -0
  141. package/src/components/help-center-pages/roadmap-page.tsx +68 -0
  142. package/src/components/layout/page-layout.tsx +11 -0
  143. package/src/components/layout/title-block.tsx +15 -2
  144. package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +30 -19
  145. package/src/components/shared/dev-section/dev-section-page.tsx +29 -19
  146. package/src/components/shared/dev-section/dev-section-view.tsx +26 -19
  147. package/src/components/shared/legal-document/legal-document-page.tsx +19 -23
  148. package/src/components/shared/product-release/release-detail-page.tsx +36 -36
  149. package/src/components/tickets/help-center-list.tsx +11 -3
  150. package/dist/chunk-4D37W55K.js.map +0 -1
  151. package/dist/chunk-OSEKWT6X.cjs.map +0 -1
  152. package/dist/chunk-TQ7CMFSY.cjs.map +0 -1
  153. package/dist/chunk-VY5YF4B7.js.map +0 -1
  154. package/dist/chunk-WFHNXCI3.cjs.map +0 -1
  155. /package/dist/{chunk-6W54MBU2.js.map → chunk-64DZ2J7Q.js.map} +0 -0
  156. /package/dist/{chunk-47JZOP7Y.js.map → chunk-6KERXOFE.js.map} +0 -0
  157. /package/dist/{chunk-JALO4TAZ.js.map → chunk-AI5X5JTD.js.map} +0 -0
  158. /package/dist/{chunk-TRSDXD23.js.map → chunk-JAZM3A7E.js.map} +0 -0
  159. /package/dist/{chunk-V4IIBNTA.js.map → chunk-LQHMXPOJ.js.map} +0 -0
  160. /package/dist/{chunk-MOOV4ORG.js.map → chunk-OKGZK6TT.js.map} +0 -0
  161. /package/dist/{chunk-E4CQ4RUG.js.map → chunk-Q4AMYLKX.js.map} +0 -0
  162. /package/dist/{chunk-2MLMZAK4.js.map → chunk-YFGDZFUG.js.map} +0 -0
  163. /package/dist/{chunk-VFIWQGJZ.js.map → chunk-Z3YORGG4.js.map} +0 -0
@@ -25,8 +25,6 @@ import {
25
25
  type OpenframeDevSectionKey,
26
26
  } from '../../../utils/dev-sections/openframe-dev-sections';
27
27
 
28
- const SECTION_HERO_ICON_CLASS = 'h-10 w-10 text-ods-accent';
29
-
30
28
  export interface DevSectionPageProps {
31
29
  sectionKey: OpenframeDevSectionKey;
32
30
  /** The page-specific list body (e.g. `<RoadmapList />`). */
@@ -48,6 +46,12 @@ export interface DevSectionPageProps {
48
46
  /** Override the hero subtitle/description. Defaults to
49
47
  * `OPENFRAME_DEV_SECTIONS[sectionKey].hero.description`. */
50
48
  subtitle?: string;
49
+ /** Render the standalone `<PageShell>` (own `<main>` + bg + max-width). Default
50
+ * `true` — the contract for marketing/hub surfaces with no app shell. Pass
51
+ * `false` when the host layout already provides the page container (e.g.
52
+ * openframe-frontend's `AppLayout` `<main>`): then only the
53
+ * `page-shell-content` padding box is rendered, avoiding a nested `<main>`. */
54
+ shell?: boolean;
51
55
  }
52
56
 
53
57
  export function DevSectionPage({
@@ -57,10 +61,10 @@ export function DevSectionPage({
57
61
  backButton,
58
62
  title,
59
63
  subtitle,
64
+ shell = true,
60
65
  }: DevSectionPageProps) {
61
66
  const router = useRouter();
62
67
  const section = OPENFRAME_DEV_SECTIONS[sectionKey];
63
- const Icon = section.icon;
64
68
 
65
69
  // Back-button config — mirrors LegalDocumentPage / ReleaseDetailPage.
66
70
  // Default: { label: 'Back to home', href: '/' }. Pass `false` to hide.
@@ -74,21 +78,27 @@ export function DevSectionPage({
74
78
  onClick: () => router.push((backButton ? backButton.href : undefined) ?? '/'),
75
79
  };
76
80
 
77
- return (
78
- <PageShell>
79
- <PageLayout backButton={backCfg}>
80
- <DevSectionView
81
- sectionKey={sectionKey}
82
- hero={{
83
- icon: <Icon className={SECTION_HERO_ICON_CLASS} />,
84
- title,
85
- description: subtitle ?? section.hero.description,
86
- }}
87
- preControls={preControls}
88
- >
89
- {children}
90
- </DevSectionView>
91
- </PageLayout>
92
- </PageShell>
81
+ const inner = (
82
+ // Unified header: title/description route through the canonical (frozen)
83
+ // `PageLayout` `TitleBlock` (text-h2) — same as FAQ / Legal / detail pages —
84
+ // so every help-center surface shares one header. `DevSectionView` then
85
+ // renders ONLY its search + filter controls (`showHeading={false}`), no
86
+ // duplicate title. (The hero icon is intentionally dropped: TitleBlock is
87
+ // frozen and renders title-only.)
88
+ <PageLayout
89
+ title={title ?? section.hero.title}
90
+ subtitle={subtitle ?? section.hero.description}
91
+ titleSize="h1"
92
+ backButton={backCfg}
93
+ >
94
+ <DevSectionView sectionKey={sectionKey} showHeading={false} preControls={preControls}>
95
+ {children}
96
+ </DevSectionView>
97
+ </PageLayout>
93
98
  );
99
+
100
+ // `shell` true → standalone `<PageShell>` (own <main> + bg + max-width).
101
+ // false → padding-only box (no nested <main>) for hosts whose layout already
102
+ // provides the container; both consume the host's `--page-shell-*` vars.
103
+ return shell ? <PageShell>{inner}</PageShell> : <div className="page-shell-content">{inner}</div>;
94
104
  }
@@ -51,9 +51,15 @@ export interface DevSectionViewProps {
51
51
  /** The page-specific list body. Reads URL params written by this
52
52
  * component (search input + filter pills). */
53
53
  children: ReactNode;
54
+ /** Render this component's own title heading (hero or compact `h2`). Default
55
+ * `true` — the tab context renders the compact heading itself. Pass `false`
56
+ * when the host already renders the title elsewhere (e.g. `DevSectionPage`
57
+ * routes it through the unified `PageLayout` `TitleBlock`): then only the
58
+ * search + filter controls + list render, with no duplicate heading. */
59
+ showHeading?: boolean;
54
60
  }
55
61
 
56
- export function DevSectionView({ sectionKey, hero, preControls, children }: DevSectionViewProps) {
62
+ export function DevSectionView({ sectionKey, hero, preControls, children, showHeading = true }: DevSectionViewProps) {
57
63
  const section = OPENFRAME_DEV_SECTIONS[sectionKey];
58
64
  const router = useRouter();
59
65
  const pathname = usePathname();
@@ -94,24 +100,25 @@ export function DevSectionView({ sectionKey, hero, preControls, children }: DevS
94
100
 
95
101
  return (
96
102
  <div className="w-full flex flex-col gap-10">
97
- {hero ? (
98
- <div className="space-y-4">
99
- <h1 className="text-h1 tracking-[-1.12px] text-ods-text-primary flex items-center gap-3">
100
- {hero.icon}
101
- {hero.title ?? section.hero.title}
102
- </h1>
103
- <p className="font-['DM_Sans'] font-medium text-[18px] leading-[28px] text-ods-text-secondary max-w-3xl">
104
- {hero.description}
105
- </p>
106
- </div>
107
- ) : (
108
- <div className="flex items-center justify-between w-full">
109
- <h2 className="font-['Azeret_Mono'] font-semibold text-[32px] md:text-[40px] lg:text-[48px] leading-[40px] md:leading-[48px] lg:leading-[56px] text-ods-text-primary tracking-[-0.64px] md:tracking-[-0.8px] lg:tracking-[-0.96px]">
110
- {section.hero.title}
111
- <span className="text-ods-accent">:</span>
112
- </h2>
113
- </div>
114
- )}
103
+ {showHeading &&
104
+ (hero ? (
105
+ <div className="space-y-4">
106
+ <h1 className="text-h1 tracking-[-1.12px] text-ods-text-primary flex items-center gap-3">
107
+ {hero.icon}
108
+ {hero.title ?? section.hero.title}
109
+ </h1>
110
+ <p className="font-['DM_Sans'] font-medium text-[18px] leading-[28px] text-ods-text-secondary max-w-3xl">
111
+ {hero.description}
112
+ </p>
113
+ </div>
114
+ ) : (
115
+ <div className="flex items-center justify-between w-full">
116
+ <h2 className="font-['Azeret_Mono'] font-semibold text-[32px] md:text-[40px] lg:text-[48px] leading-[40px] md:leading-[48px] lg:leading-[56px] text-ods-text-primary tracking-[-0.64px] md:tracking-[-0.8px] lg:tracking-[-0.96px]">
117
+ {section.hero.title}
118
+ <span className="text-ods-accent">:</span>
119
+ </h2>
120
+ </div>
121
+ ))}
115
122
 
116
123
  {preControls}
117
124
 
@@ -18,7 +18,7 @@
18
18
  */
19
19
 
20
20
  import type { ComponentType } from 'react';
21
- import { PageShell, PageLayout, PageHeading } from '../../ui';
21
+ import { PageShell, PageLayout } from '../../ui';
22
22
  import { RichMarkdownRenderer } from '../../ui/rich-markdown-renderer';
23
23
  import { useRouter } from '../../../embed-shims/next-navigation';
24
24
  import { useLegalDocs, type LegalDocument } from './use-legal-docs';
@@ -63,6 +63,10 @@ export interface LegalDocumentPageProps {
63
63
  /** Back-button config — same pattern as `DevSectionPage`. Pass `false`
64
64
  * to hide. Default `{ label: 'Back to home', href: '/' }`. */
65
65
  backButton?: { label?: string; href?: string } | false;
66
+ /** Render the standalone `<PageShell>`. Default true. Pass false when the host
67
+ * layout already provides the page container — only the padding box renders,
68
+ * avoiding a nested `<main>`. */
69
+ shell?: boolean;
66
70
  }
67
71
 
68
72
  export function LegalDocumentPage({
@@ -78,6 +82,7 @@ export function LegalDocumentPage({
78
82
  apiEndpoint,
79
83
  MarkdownRenderer = RichMarkdownRenderer,
80
84
  backButton,
85
+ shell = true,
81
86
  }: LegalDocumentPageProps) {
82
87
  const router = useRouter();
83
88
  const { data, isLoading, error } = useLegalDocs(docType, { initialData, apiEndpoint });
@@ -97,28 +102,18 @@ export function LegalDocumentPage({
97
102
  data?.lastSynced != null ? formatLegalDate(data.lastSynced) : null;
98
103
  const effectiveLastUpdatedLabel = initialLastUpdatedLabel ?? fallbackLastUpdatedLabel;
99
104
 
100
- // Title with accent-colon trailing dot matches knowledge-hub typography
101
- const customTitle = (
102
- <div className="flex flex-col gap-4">
103
- <PageHeading>
104
- <span>{title}</span>
105
- <span className="text-ods-accent">.</span>
106
- </PageHeading>
107
- <p className="font-['DM_Sans'] text-base md:text-lg text-ods-text-secondary max-w-2xl">
108
- {effectiveLastUpdatedLabel ? `Last Updated: ${effectiveLastUpdatedLabel}` : fallbackDescription}
109
- {data?.sourceFile && (
110
- <span className="block text-sm mt-1 opacity-75">Source: {data.sourceFile}</span>
111
- )}
112
- </p>
113
- </div>
114
- );
105
+ // Subtitle routes through the frozen `PageLayout` `TitleBlock` (text-h2 title
106
+ // + subtitle) — unified header across all help-center pages. Shows the
107
+ // last-updated date when known, else the fallback description.
108
+ const subtitle = effectiveLastUpdatedLabel ? `Last Updated: ${effectiveLastUpdatedLabel}` : fallbackDescription;
115
109
 
116
- return (
117
- <PageShell>
118
- <PageLayout backButton={backCfg}>
119
- <div className="flex flex-col gap-4">{customTitle}</div>
110
+ const inner = (
111
+ <PageLayout title={title} subtitle={subtitle} backButton={backCfg} titleSize="h1">
112
+ {data?.sourceFile && (
113
+ <p className="font-['DM_Sans'] text-sm text-ods-text-secondary opacity-75">Source: {data.sourceFile}</p>
114
+ )}
120
115
 
121
- <div className="flex flex-col lg:flex-row gap-6 lg:gap-10 items-start flex-1">
116
+ <div className="flex flex-col lg:flex-row gap-6 lg:gap-10 items-start flex-1">
122
117
  <div className="flex-1">
123
118
  <div className="w-full">
124
119
  <article className="space-y-2">
@@ -172,7 +167,8 @@ export function LegalDocumentPage({
172
167
  </div>
173
168
  </div>
174
169
  </div>
175
- </PageLayout>
176
- </PageShell>
170
+ </PageLayout>
177
171
  );
172
+
173
+ return shell ? <PageShell>{inner}</PageShell> : <div className="page-shell-content">{inner}</div>;
178
174
  }
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { useState, useEffect, ComponentType } from 'react';
3
+ import { useState, useEffect, ComponentType, type ReactNode } from 'react';
4
4
  import Link from '../../../embed-shims/next-link';
5
5
  import { useRouter } from '../../../embed-shims/next-navigation';
6
6
  import { Card, CardContent } from '../../ui/card';
@@ -108,6 +108,15 @@ export interface ReleaseDetailPageProps {
108
108
  /** Link target for the author name in the metadata grid — the host
109
109
  * computes it (public author page; absent ⇒ plain text). */
110
110
  authorHref?: string;
111
+ /** Optional slot rendered inside the page chrome, BELOW the article body —
112
+ * e.g. the hub's end-of-article author byline + related-content / FAQ rail.
113
+ * Lets the hub mount this page directly (no local wrapper component) while
114
+ * embedders that don't have those extras simply omit it. */
115
+ relatedContent?: ReactNode;
116
+ /** Render the standalone `<PageShell>`. Default true. Pass false when the host
117
+ * layout already provides the page container — only the padding box renders,
118
+ * avoiding a nested `<main>`. */
119
+ shell?: boolean;
111
120
  }
112
121
 
113
122
  // Default renderer = the lib's `RichMarkdownRenderer` so out-of-the-box
@@ -128,9 +137,15 @@ export function ReleaseDetailPage({
128
137
  VideoDisplaySection,
129
138
  roadmapApiEndpoint = '/api/roadmap',
130
139
  deliveryApiEndpoint = '/api/delivery',
131
- backButton
140
+ backButton,
141
+ relatedContent,
142
+ shell = true
132
143
  }: ReleaseDetailPageProps) {
133
144
  const router = useRouter();
145
+ // `shell` true → standalone `<PageShell>`; false → padding-only box (no nested
146
+ // <main>) for hosts whose layout already provides the container.
147
+ const renderShell = (node: ReactNode) =>
148
+ shell ? <PageShell>{node}</PageShell> : <div className="page-shell-content">{node}</div>;
134
149
  // Use pre-fetched data if provided (admin preview), otherwise fetch via hook (public)
135
150
  const { data: fetchedRelease, error, isLoading } = useRelease(initialData ? undefined : slug);
136
151
  const release = (initialData || fetchedRelease) as Record<string, unknown> | undefined;
@@ -191,25 +206,21 @@ export function ReleaseDetailPage({
191
206
  if (!initialData && isLoading) {
192
207
  // `bare` + `PageShell` so the loading state matches the loaded page's full
193
208
  // width / padding / min-height (the wrapper supplies the page chrome).
194
- return (
195
- <PageShell>
196
- {/* Match the loaded page's top offset (TitleBlock's
197
- `pt-[var(--spacing-system-l)]`) so content doesn't jump on load. */}
198
- <div className="pt-[var(--spacing-system-l)]">
199
- <DetailPageSkeleton bare metadataColumns={4} showImageGallery={true} />
200
- </div>
201
- </PageShell>
209
+ return renderShell(
210
+ // Match the loaded page's top offset (TitleBlock's
211
+ // `pt-[var(--spacing-system-l)]`) so content doesn't jump on load.
212
+ <div className="pt-[var(--spacing-system-l)]">
213
+ <DetailPageSkeleton bare metadataColumns={4} showImageGallery={true} />
214
+ </div>
202
215
  );
203
216
  }
204
217
 
205
218
  if (error || !release) {
206
- return (
207
- <PageShell>
208
- <div className="text-center py-16">
209
- <h1 className="text-4xl font-bold text-ods-text-primary mb-4">Release Not Found</h1>
210
- <p className="text-xl text-ods-text-secondary">The release you&apos;re looking for doesn&apos;t exist.</p>
211
- </div>
212
- </PageShell>
219
+ return renderShell(
220
+ <div className="text-center py-16">
221
+ <h1 className="text-4xl font-bold text-ods-text-primary mb-4">Release Not Found</h1>
222
+ <p className="text-xl text-ods-text-secondary">The release you&apos;re looking for doesn&apos;t exist.</p>
223
+ </div>
213
224
  );
214
225
  }
215
226
 
@@ -241,29 +252,16 @@ export function ReleaseDetailPage({
241
252
  const bugFixed = release.bugs_fixed as ChangelogEntry[] | undefined;
242
253
  const improvements = release.improvements as ChangelogEntry[] | undefined;
243
254
 
244
- return (
245
- <PageShell>
246
- <PageLayout
255
+ return renderShell(
256
+ <PageLayout
257
+ title={releaseTitle}
258
+ subtitle={`Version: ${releaseVersion}`}
259
+ titleSize="h1"
247
260
  backButton={
248
261
  showBackButton ? { label: backLabel, onClick: () => router.push(backHref) } : undefined
249
262
  }
250
263
  >
251
264
  <div className="space-y-6 md:space-y-8">
252
- {/* Title Block */}
253
- <div className="flex flex-col md:flex-row md:items-end gap-4 w-full">
254
- <div className="flex-1 flex flex-col gap-2">
255
- {/* Title */}
256
- <h1 className="text-h1 tracking-[-1.12px] text-ods-text-primary">
257
- {releaseTitle}
258
- </h1>
259
-
260
- {/* Version */}
261
- <p className="text-h4 text-ods-text-secondary">
262
- Version: {releaseVersion}
263
- </p>
264
- </div>
265
- </div>
266
-
267
265
  {/* Tags — flat product_release_tags[] from entity_tags */}
268
266
  <EntityTagBadges tags={release.product_release_tags as TagAssoc[] | undefined} />
269
267
 
@@ -568,7 +566,9 @@ export function ReleaseDetailPage({
568
566
  </div>
569
567
  )}
570
568
  </div>
569
+
570
+ {/* Host slot — end-of-article byline + related-content / FAQ rail. */}
571
+ {relatedContent}
571
572
  </PageLayout>
572
- </PageShell>
573
573
  );
574
574
  }
@@ -59,9 +59,13 @@ export interface HelpCenterListProps {
59
59
  * the `tickets` section copy ("Help Center"). Set this to brand the surface
60
60
  * for an embed that wants its own label (e.g. "Support Tickets"). */
61
61
  title?: string
62
+ /** Render the standalone `<PageShell>` (forwarded to the internal
63
+ * `DevSectionPage`). Default true. Pass false when the host layout already
64
+ * provides the page container (avoids a nested `<main>`). */
65
+ shell?: boolean
62
66
  }
63
67
 
64
- export function HelpCenterList({ toast = defaultToast, backButton, title }: HelpCenterListProps = {}) {
68
+ export function HelpCenterList({ toast = defaultToast, backButton, title, shell }: HelpCenterListProps = {}) {
65
69
  const identity = useChatIdentity()
66
70
  const searchParams = useSearchParams()
67
71
  const router = useRouter()
@@ -93,6 +97,7 @@ export function HelpCenterList({ toast = defaultToast, backButton, title }: Help
93
97
  sectionKey="tickets"
94
98
  backButton={backButton}
95
99
  title={title}
100
+ shell={shell}
96
101
  preControls={<HelpCenterCreateFormSkeleton />}
97
102
  >
98
103
  <DevCardRowSkeletonList />
@@ -101,7 +106,7 @@ export function HelpCenterList({ toast = defaultToast, backButton, title }: Help
101
106
  }
102
107
  if (identity.authTier === 'anon' || !identity.user?.email) {
103
108
  return (
104
- <DevSectionPage sectionKey="tickets" backButton={backButton} title={title}>
109
+ <DevSectionPage sectionKey="tickets" backButton={backButton} title={title} shell={shell}>
105
110
  <EmptyState
106
111
  type="generic"
107
112
  title="Sign in to manage tickets"
@@ -138,6 +143,7 @@ export function HelpCenterList({ toast = defaultToast, backButton, title }: Help
138
143
  sessionEmail={sessionEmail}
139
144
  backButton={backButton}
140
145
  title={title}
146
+ shell={shell}
141
147
  />
142
148
  )
143
149
  }
@@ -156,6 +162,7 @@ interface AuthedProps {
156
162
  sessionEmail: string
157
163
  backButton?: { label?: string; href?: string } | false
158
164
  title?: string
165
+ shell?: boolean
159
166
  }
160
167
 
161
168
  function HelpCenterListAuthed({
@@ -171,6 +178,7 @@ function HelpCenterListAuthed({
171
178
  sessionEmail,
172
179
  backButton,
173
180
  title,
181
+ shell,
174
182
  }: AuthedProps) {
175
183
  const queryClient = useQueryClient()
176
184
  const [optimisticTickets, setOptimisticTickets] = useState<OptimisticTicket[]>([])
@@ -398,7 +406,7 @@ function HelpCenterListAuthed({
398
406
  )
399
407
 
400
408
  return (
401
- <DevSectionPage sectionKey="tickets" backButton={backButton} title={title} preControls={form}>
409
+ <DevSectionPage sectionKey="tickets" backButton={backButton} title={title} shell={shell} preControls={form}>
402
410
  {body}
403
411
  </DevSectionPage>
404
412
  )