@burdenoff/website-sdk 2026.1027.1 → 2026.1028.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.
@@ -45,6 +45,16 @@ interface CareersApplyFormProps {
45
45
  }
46
46
  declare function CareersApplyForm({ productName, recaptchaSiteKey, positions, postings, heroTitle, heroDescription, headingLevel, maxResumeBytes, className, seo, }: CareersApplyFormProps): react.JSX.Element;
47
47
 
48
+ /**
49
+ * A customer's written approval of a case study (BOFF-9971). Public callers
50
+ * receive only the approval date; who approved it and where the approval is
51
+ * filed stay with the people who manage content pages.
52
+ */
53
+ interface CustomerSignOffData {
54
+ signedAt: string;
55
+ signedBy?: string | null;
56
+ documentRef?: string | null;
57
+ }
48
58
  interface ContentPageData {
49
59
  id: string;
50
60
  title: string;
@@ -64,6 +74,7 @@ interface ContentPageData {
64
74
  readingTime?: number | null;
65
75
  featured?: boolean;
66
76
  category?: string | null;
77
+ customerSignOff?: CustomerSignOffData | null;
67
78
  }
68
79
  interface ContentPageListItem {
69
80
  id: string;
@@ -79,6 +90,7 @@ interface ContentPageListItem {
79
90
  featured?: boolean;
80
91
  category?: string | null;
81
92
  seoDescription?: string | null;
93
+ customerSignOff?: CustomerSignOffData | null;
82
94
  }
83
95
  interface ContentPageListData {
84
96
  items: ContentPageListItem[];
@@ -422,6 +434,19 @@ interface CaseStudyPageProps {
422
434
  contactLabel?: string;
423
435
  className?: string;
424
436
  }
437
+ /**
438
+ * True when the customer has approved the story in writing (BOFF-9971).
439
+ *
440
+ * The CMS already refuses to serve an unapproved case study; this check is the
441
+ * second lock, so no website renders one even against a CMS build that predates
442
+ * the gate. A case study states results on a customer's behalf, and we never
443
+ * publish numbers a customer has not signed off.
444
+ */
445
+ declare function hasCustomerSignOff(page: {
446
+ customerSignOff?: {
447
+ signedAt?: string | null;
448
+ } | null;
449
+ }): boolean;
425
450
  declare function CaseStudiesListPage(props: CaseStudiesListPageProps): react.JSX.Element;
426
451
  declare function CaseStudyPage(props: CaseStudyPageProps): react.JSX.Element;
427
452
 
@@ -534,4 +559,4 @@ interface ContractsPageProps {
534
559
  }
535
560
  declare function ContractsPage(props: ContractsPageProps): react.JSX.Element;
536
561
 
537
- export { type PressKitAssetCategory as A, BlogListPage as B, type CareersPostingOption as C, PressKitPage as D, type PressKitPageProps as E, type FetchLegalContentOptions as F, type PublicInterviewRoundConfig as G, type PublicJobPosting as H, type UsePublicJobPostingsOptions as I, fetchLegalContent as J, fetchLegalContentPage as K, LEGAL_DOCUMENTS as L, Markdown as M, fetchPublicJobPosting as N, fetchPublicJobPostings as O, type PressKitAsset as P, fetchPublicJobPostingsForSubmission as Q, findLegalDocument as R, formatLegalDate as S, splitLeadingHeading as T, type UsePublicJobPostingOptions as U, useContentPage as V, useContentPages as W, usePublicJobPosting as X, usePublicJobPostings as Y, usePublicJobPostingsForSubmission as Z, type UseContentPageOptions as _, type BlogListPageProps as a, BlogPostPage as b, type BlogPostPageProps as c, CareersApplyForm as d, type CareersApplyFormProps as e, CaseStudiesListPage as f, type CaseStudiesListPageProps as g, CaseStudyPage as h, type CaseStudyPageProps as i, type ChangelogEntry as j, ChangelogPage as k, type ChangelogPageProps as l, type ContentPageData as m, type ContentPageListData as n, type ContentPageListItem as o, ContentRenderer as p, type ContractAsset as q, ContractsPage as r, type ContractsPageProps as s, LEGAL_DOCUMENT_KEYS as t, type LegalContentEntry as u, type LegalDocument as v, type LegalDocumentKey as w, LegalPage as x, type LegalPageProps as y, type MarkdownProps as z };
562
+ export { usePublicJobPostingsForSubmission as $, type MarkdownProps as A, BlogListPage as B, type CareersPostingOption as C, type PressKitAssetCategory as D, PressKitPage as E, type FetchLegalContentOptions as F, type PressKitPageProps as G, type PublicInterviewRoundConfig as H, type PublicJobPosting as I, type UsePublicJobPostingsOptions as J, fetchLegalContent as K, LEGAL_DOCUMENTS as L, Markdown as M, fetchLegalContentPage as N, fetchPublicJobPosting as O, type PressKitAsset as P, fetchPublicJobPostings as Q, fetchPublicJobPostingsForSubmission as R, findLegalDocument as S, formatLegalDate as T, type UsePublicJobPostingOptions as U, hasCustomerSignOff as V, splitLeadingHeading as W, useContentPage as X, useContentPages as Y, usePublicJobPosting as Z, usePublicJobPostings as _, type BlogListPageProps as a, type UseContentPageOptions as a0, BlogPostPage as b, type BlogPostPageProps as c, CareersApplyForm as d, type CareersApplyFormProps as e, CaseStudiesListPage as f, type CaseStudiesListPageProps as g, CaseStudyPage as h, type CaseStudyPageProps as i, type ChangelogEntry as j, ChangelogPage as k, type ChangelogPageProps as l, type ContentPageData as m, type ContentPageListData as n, type ContentPageListItem as o, ContentRenderer as p, type ContractAsset as q, ContractsPage as r, type ContractsPageProps as s, type CustomerSignOffData as t, LEGAL_DOCUMENT_KEYS as u, type LegalContentEntry as v, type LegalDocument as w, type LegalDocumentKey as x, LegalPage as y, type LegalPageProps as z };
@@ -45,6 +45,16 @@ interface CareersApplyFormProps {
45
45
  }
46
46
  declare function CareersApplyForm({ productName, recaptchaSiteKey, positions, postings, heroTitle, heroDescription, headingLevel, maxResumeBytes, className, seo, }: CareersApplyFormProps): react.JSX.Element;
47
47
 
48
+ /**
49
+ * A customer's written approval of a case study (BOFF-9971). Public callers
50
+ * receive only the approval date; who approved it and where the approval is
51
+ * filed stay with the people who manage content pages.
52
+ */
53
+ interface CustomerSignOffData {
54
+ signedAt: string;
55
+ signedBy?: string | null;
56
+ documentRef?: string | null;
57
+ }
48
58
  interface ContentPageData {
49
59
  id: string;
50
60
  title: string;
@@ -64,6 +74,7 @@ interface ContentPageData {
64
74
  readingTime?: number | null;
65
75
  featured?: boolean;
66
76
  category?: string | null;
77
+ customerSignOff?: CustomerSignOffData | null;
67
78
  }
68
79
  interface ContentPageListItem {
69
80
  id: string;
@@ -79,6 +90,7 @@ interface ContentPageListItem {
79
90
  featured?: boolean;
80
91
  category?: string | null;
81
92
  seoDescription?: string | null;
93
+ customerSignOff?: CustomerSignOffData | null;
82
94
  }
83
95
  interface ContentPageListData {
84
96
  items: ContentPageListItem[];
@@ -422,6 +434,19 @@ interface CaseStudyPageProps {
422
434
  contactLabel?: string;
423
435
  className?: string;
424
436
  }
437
+ /**
438
+ * True when the customer has approved the story in writing (BOFF-9971).
439
+ *
440
+ * The CMS already refuses to serve an unapproved case study; this check is the
441
+ * second lock, so no website renders one even against a CMS build that predates
442
+ * the gate. A case study states results on a customer's behalf, and we never
443
+ * publish numbers a customer has not signed off.
444
+ */
445
+ declare function hasCustomerSignOff(page: {
446
+ customerSignOff?: {
447
+ signedAt?: string | null;
448
+ } | null;
449
+ }): boolean;
425
450
  declare function CaseStudiesListPage(props: CaseStudiesListPageProps): react.JSX.Element;
426
451
  declare function CaseStudyPage(props: CaseStudyPageProps): react.JSX.Element;
427
452
 
@@ -534,4 +559,4 @@ interface ContractsPageProps {
534
559
  }
535
560
  declare function ContractsPage(props: ContractsPageProps): react.JSX.Element;
536
561
 
537
- export { type PressKitAssetCategory as A, BlogListPage as B, type CareersPostingOption as C, PressKitPage as D, type PressKitPageProps as E, type FetchLegalContentOptions as F, type PublicInterviewRoundConfig as G, type PublicJobPosting as H, type UsePublicJobPostingsOptions as I, fetchLegalContent as J, fetchLegalContentPage as K, LEGAL_DOCUMENTS as L, Markdown as M, fetchPublicJobPosting as N, fetchPublicJobPostings as O, type PressKitAsset as P, fetchPublicJobPostingsForSubmission as Q, findLegalDocument as R, formatLegalDate as S, splitLeadingHeading as T, type UsePublicJobPostingOptions as U, useContentPage as V, useContentPages as W, usePublicJobPosting as X, usePublicJobPostings as Y, usePublicJobPostingsForSubmission as Z, type UseContentPageOptions as _, type BlogListPageProps as a, BlogPostPage as b, type BlogPostPageProps as c, CareersApplyForm as d, type CareersApplyFormProps as e, CaseStudiesListPage as f, type CaseStudiesListPageProps as g, CaseStudyPage as h, type CaseStudyPageProps as i, type ChangelogEntry as j, ChangelogPage as k, type ChangelogPageProps as l, type ContentPageData as m, type ContentPageListData as n, type ContentPageListItem as o, ContentRenderer as p, type ContractAsset as q, ContractsPage as r, type ContractsPageProps as s, LEGAL_DOCUMENT_KEYS as t, type LegalContentEntry as u, type LegalDocument as v, type LegalDocumentKey as w, LegalPage as x, type LegalPageProps as y, type MarkdownProps as z };
562
+ export { usePublicJobPostingsForSubmission as $, type MarkdownProps as A, BlogListPage as B, type CareersPostingOption as C, type PressKitAssetCategory as D, PressKitPage as E, type FetchLegalContentOptions as F, type PressKitPageProps as G, type PublicInterviewRoundConfig as H, type PublicJobPosting as I, type UsePublicJobPostingsOptions as J, fetchLegalContent as K, LEGAL_DOCUMENTS as L, Markdown as M, fetchLegalContentPage as N, fetchPublicJobPosting as O, type PressKitAsset as P, fetchPublicJobPostings as Q, fetchPublicJobPostingsForSubmission as R, findLegalDocument as S, formatLegalDate as T, type UsePublicJobPostingOptions as U, hasCustomerSignOff as V, splitLeadingHeading as W, useContentPage as X, useContentPages as Y, usePublicJobPosting as Z, usePublicJobPostings as _, type BlogListPageProps as a, type UseContentPageOptions as a0, BlogPostPage as b, type BlogPostPageProps as c, CareersApplyForm as d, type CareersApplyFormProps as e, CaseStudiesListPage as f, type CaseStudiesListPageProps as g, CaseStudyPage as h, type CaseStudyPageProps as i, type ChangelogEntry as j, ChangelogPage as k, type ChangelogPageProps as l, type ContentPageData as m, type ContentPageListData as n, type ContentPageListItem as o, ContentRenderer as p, type ContractAsset as q, ContractsPage as r, type ContractsPageProps as s, type CustomerSignOffData as t, LEGAL_DOCUMENT_KEYS as u, type LegalContentEntry as v, type LegalDocument as w, type LegalDocumentKey as x, LegalPage as y, type LegalPageProps as z };
package/dist/index.d.mts CHANGED
@@ -2,8 +2,8 @@ export { G as GraphQLResponse, W as WebSDKClient, a as WebSDKClientConfig, b as
2
2
  export { W as WebSDKProvider, a as WebSDKProviderProps, u as useWebSDK, b as useWebSDKConfig } from './provider-MFV1NVqZ.mjs';
3
3
  export { ContactCategory, ContactPage, ContactPageProps, DEFAULT_CONTACT_CATEGORIES } from './components/contact.mjs';
4
4
  export { PartnersPage, PartnersPageProps } from './components/partners.mjs';
5
- import { C as CareersPostingOption } from './index-Ctws7kGs.mjs';
6
- export { B as BlogListPage, a as BlogListPageProps, b as BlogPostPage, c as BlogPostPageProps, d as CareersApplyForm, e as CareersApplyFormProps, f as CaseStudiesListPage, g as CaseStudiesListPageProps, h as CaseStudyPage, i as CaseStudyPageProps, j as ChangelogEntry, k as ChangelogPage, l as ChangelogPageProps, m as ContentPageData, n as ContentPageListData, o as ContentPageListItem, p as ContentRenderer, q as ContractAsset, r as ContractsPage, s as ContractsPageProps, F as FetchLegalContentOptions, L as LEGAL_DOCUMENTS, t as LEGAL_DOCUMENT_KEYS, u as LegalContentEntry, v as LegalDocument, w as LegalDocumentKey, x as LegalPage, y as LegalPageProps, M as Markdown, z as MarkdownProps, P as PressKitAsset, A as PressKitAssetCategory, D as PressKitPage, E as PressKitPageProps, G as PublicInterviewRoundConfig, H as PublicJobPosting, U as UsePublicJobPostingOptions, I as UsePublicJobPostingsOptions, J as fetchLegalContent, K as fetchLegalContentPage, N as fetchPublicJobPosting, O as fetchPublicJobPostings, Q as fetchPublicJobPostingsForSubmission, R as findLegalDocument, S as formatLegalDate, T as splitLeadingHeading, V as useContentPage, W as useContentPages, X as usePublicJobPosting, Y as usePublicJobPostings, Z as usePublicJobPostingsForSubmission } from './index-Ctws7kGs.mjs';
5
+ import { C as CareersPostingOption } from './index-Chw0pDXS.mjs';
6
+ export { B as BlogListPage, a as BlogListPageProps, b as BlogPostPage, c as BlogPostPageProps, d as CareersApplyForm, e as CareersApplyFormProps, f as CaseStudiesListPage, g as CaseStudiesListPageProps, h as CaseStudyPage, i as CaseStudyPageProps, j as ChangelogEntry, k as ChangelogPage, l as ChangelogPageProps, m as ContentPageData, n as ContentPageListData, o as ContentPageListItem, p as ContentRenderer, q as ContractAsset, r as ContractsPage, s as ContractsPageProps, t as CustomerSignOffData, F as FetchLegalContentOptions, L as LEGAL_DOCUMENTS, u as LEGAL_DOCUMENT_KEYS, v as LegalContentEntry, w as LegalDocument, x as LegalDocumentKey, y as LegalPage, z as LegalPageProps, M as Markdown, A as MarkdownProps, P as PressKitAsset, D as PressKitAssetCategory, E as PressKitPage, G as PressKitPageProps, H as PublicInterviewRoundConfig, I as PublicJobPosting, U as UsePublicJobPostingOptions, J as UsePublicJobPostingsOptions, K as fetchLegalContent, N as fetchLegalContentPage, O as fetchPublicJobPosting, Q as fetchPublicJobPostings, R as fetchPublicJobPostingsForSubmission, S as findLegalDocument, T as formatLegalDate, V as hasCustomerSignOff, W as splitLeadingHeading, X as useContentPage, Y as useContentPages, Z as usePublicJobPosting, _ as usePublicJobPostings, $ as usePublicJobPostingsForSubmission } from './index-Chw0pDXS.mjs';
7
7
  import * as react from 'react';
8
8
  import { ComponentType, ReactNode, Dispatch, SetStateAction, RefObject } from 'react';
9
9
  export { NewsletterForm, NewsletterFormProps, NewsletterPage, NewsletterPageProps } from './components/newsletter.mjs';
@@ -32,6 +32,7 @@ import { VariantProps } from 'class-variance-authority';
32
32
  import * as LabelPrimitive from '@radix-ui/react-label';
33
33
  export { B as BrowseStoreResult, G as GroupedStoreProducts, P as ProductCard, a as ProductCategory, R as RatingDistribution, S as SearchSuggestion, b as StarRating, c as StoreBrowsePage, d as StoreBrowsePageProps, e as StoreFacet, f as StoreFacets, g as StoreHomePage, h as StoreHomePageProps, i as StorePageProps, j as StoreProduct, k as StoreProductDetailPage, l as StoreProductDetailPageProps, m as StoreProductDetails, n as StorePublisher, o as StoreReview, p as StoreTerminology, q as StoreTypeFilter, T as TypeBadge, r as formatDownloads, s as formatPrice } from './product-card-CGxETsFa.mjs';
34
34
  export { EXPLORE_RECAPTCHA_ACTION, FOOTERLESS_ROUTES, PRODUCT_SLUG_ALIASES, PRODUCT_SLUG_EMIT, PRODUCT_SLUG_RENAMES, ProductSlugGeneration, ProductSlugRename, RecaptchaV3, cn, emitProductSlug, getRecaptchaV3Token, isFooterlessPath, isSameProductSlug, legacyProductSlug, loadRecaptchaV3, normalizeProductSlug, normalizeRoutePath, productSlugVariants, shouldRenderSiteFooter } from './utils/index.mjs';
35
+ export { ComparisonClaim, ComparisonSource, DEFAULT_COMPARISON_MAX_AGE_DAYS, FreshnessIssue, FreshnessOptions, FreshnessProblem, NonEmptySources, Sourced, checkComparisonFreshness, formatFreshnessReport, latestVerification, oldestVerification, parseVerifiedAt } from './comparison/index.mjs';
35
36
  import 'clsx';
36
37
 
37
38
  interface CareersSubmissionFormProps {
package/dist/index.d.ts CHANGED
@@ -2,8 +2,8 @@ export { G as GraphQLResponse, W as WebSDKClient, a as WebSDKClientConfig, b as
2
2
  export { W as WebSDKProvider, a as WebSDKProviderProps, u as useWebSDK, b as useWebSDKConfig } from './provider-Dwgz4d0r.js';
3
3
  export { ContactCategory, ContactPage, ContactPageProps, DEFAULT_CONTACT_CATEGORIES } from './components/contact.js';
4
4
  export { PartnersPage, PartnersPageProps } from './components/partners.js';
5
- import { C as CareersPostingOption } from './index-CB0Uw1xD.js';
6
- export { B as BlogListPage, a as BlogListPageProps, b as BlogPostPage, c as BlogPostPageProps, d as CareersApplyForm, e as CareersApplyFormProps, f as CaseStudiesListPage, g as CaseStudiesListPageProps, h as CaseStudyPage, i as CaseStudyPageProps, j as ChangelogEntry, k as ChangelogPage, l as ChangelogPageProps, m as ContentPageData, n as ContentPageListData, o as ContentPageListItem, p as ContentRenderer, q as ContractAsset, r as ContractsPage, s as ContractsPageProps, F as FetchLegalContentOptions, L as LEGAL_DOCUMENTS, t as LEGAL_DOCUMENT_KEYS, u as LegalContentEntry, v as LegalDocument, w as LegalDocumentKey, x as LegalPage, y as LegalPageProps, M as Markdown, z as MarkdownProps, P as PressKitAsset, A as PressKitAssetCategory, D as PressKitPage, E as PressKitPageProps, G as PublicInterviewRoundConfig, H as PublicJobPosting, U as UsePublicJobPostingOptions, I as UsePublicJobPostingsOptions, J as fetchLegalContent, K as fetchLegalContentPage, N as fetchPublicJobPosting, O as fetchPublicJobPostings, Q as fetchPublicJobPostingsForSubmission, R as findLegalDocument, S as formatLegalDate, T as splitLeadingHeading, V as useContentPage, W as useContentPages, X as usePublicJobPosting, Y as usePublicJobPostings, Z as usePublicJobPostingsForSubmission } from './index-CB0Uw1xD.js';
5
+ import { C as CareersPostingOption } from './index-87BLB-Hj.js';
6
+ export { B as BlogListPage, a as BlogListPageProps, b as BlogPostPage, c as BlogPostPageProps, d as CareersApplyForm, e as CareersApplyFormProps, f as CaseStudiesListPage, g as CaseStudiesListPageProps, h as CaseStudyPage, i as CaseStudyPageProps, j as ChangelogEntry, k as ChangelogPage, l as ChangelogPageProps, m as ContentPageData, n as ContentPageListData, o as ContentPageListItem, p as ContentRenderer, q as ContractAsset, r as ContractsPage, s as ContractsPageProps, t as CustomerSignOffData, F as FetchLegalContentOptions, L as LEGAL_DOCUMENTS, u as LEGAL_DOCUMENT_KEYS, v as LegalContentEntry, w as LegalDocument, x as LegalDocumentKey, y as LegalPage, z as LegalPageProps, M as Markdown, A as MarkdownProps, P as PressKitAsset, D as PressKitAssetCategory, E as PressKitPage, G as PressKitPageProps, H as PublicInterviewRoundConfig, I as PublicJobPosting, U as UsePublicJobPostingOptions, J as UsePublicJobPostingsOptions, K as fetchLegalContent, N as fetchLegalContentPage, O as fetchPublicJobPosting, Q as fetchPublicJobPostings, R as fetchPublicJobPostingsForSubmission, S as findLegalDocument, T as formatLegalDate, V as hasCustomerSignOff, W as splitLeadingHeading, X as useContentPage, Y as useContentPages, Z as usePublicJobPosting, _ as usePublicJobPostings, $ as usePublicJobPostingsForSubmission } from './index-87BLB-Hj.js';
7
7
  import * as react from 'react';
8
8
  import { ComponentType, ReactNode, Dispatch, SetStateAction, RefObject } from 'react';
9
9
  export { NewsletterForm, NewsletterFormProps, NewsletterPage, NewsletterPageProps } from './components/newsletter.js';
@@ -32,6 +32,7 @@ import { VariantProps } from 'class-variance-authority';
32
32
  import * as LabelPrimitive from '@radix-ui/react-label';
33
33
  export { B as BrowseStoreResult, G as GroupedStoreProducts, P as ProductCard, a as ProductCategory, R as RatingDistribution, S as SearchSuggestion, b as StarRating, c as StoreBrowsePage, d as StoreBrowsePageProps, e as StoreFacet, f as StoreFacets, g as StoreHomePage, h as StoreHomePageProps, i as StorePageProps, j as StoreProduct, k as StoreProductDetailPage, l as StoreProductDetailPageProps, m as StoreProductDetails, n as StorePublisher, o as StoreReview, p as StoreTerminology, q as StoreTypeFilter, T as TypeBadge, r as formatDownloads, s as formatPrice } from './product-card-CGxETsFa.js';
34
34
  export { EXPLORE_RECAPTCHA_ACTION, FOOTERLESS_ROUTES, PRODUCT_SLUG_ALIASES, PRODUCT_SLUG_EMIT, PRODUCT_SLUG_RENAMES, ProductSlugGeneration, ProductSlugRename, RecaptchaV3, cn, emitProductSlug, getRecaptchaV3Token, isFooterlessPath, isSameProductSlug, legacyProductSlug, loadRecaptchaV3, normalizeProductSlug, normalizeRoutePath, productSlugVariants, shouldRenderSiteFooter } from './utils/index.js';
35
+ export { ComparisonClaim, ComparisonSource, DEFAULT_COMPARISON_MAX_AGE_DAYS, FreshnessIssue, FreshnessOptions, FreshnessProblem, NonEmptySources, Sourced, checkComparisonFreshness, formatFreshnessReport, latestVerification, oldestVerification, parseVerifiedAt } from './comparison/index.js';
35
36
  import 'clsx';
36
37
 
37
38
  interface CareersSubmissionFormProps {
package/dist/index.js CHANGED
@@ -16628,6 +16628,10 @@ function BlogPostPage(props) {
16628
16628
  ] });
16629
16629
  }
16630
16630
  var CASE_STUDY_TAG = "case-study";
16631
+ function hasCustomerSignOff(page) {
16632
+ const signedAt = page.customerSignOff?.signedAt;
16633
+ return typeof signedAt === "string" && !Number.isNaN(new Date(signedAt).getTime());
16634
+ }
16631
16635
  var ANIMATION_STYLES = `
16632
16636
  @keyframes csFadeUp { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: translateY(0); } }
16633
16637
  @keyframes csFloat { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-14px); } }
@@ -16657,6 +16661,9 @@ var CASE_STUDIES_QUERY = `
16657
16661
  readingTime
16658
16662
  featured
16659
16663
  category
16664
+ customerSignOff {
16665
+ signedAt
16666
+ }
16660
16667
  }
16661
16668
  total
16662
16669
  hasMore
@@ -16683,6 +16690,9 @@ var CASE_STUDY_QUERY = `
16683
16690
  readingTime
16684
16691
  featured
16685
16692
  category
16693
+ customerSignOff {
16694
+ signedAt
16695
+ }
16686
16696
  }
16687
16697
  }
16688
16698
  `;
@@ -17053,11 +17063,15 @@ function CaseStudiesListPage(props) {
17053
17063
  setPosts([]);
17054
17064
  return;
17055
17065
  }
17056
- const caseStudies = (result.data?.publicContentPages?.items || []).filter(
17057
- (p) => p.tags?.includes(CASE_STUDY_TAG)
17066
+ const items = result.data?.publicContentPages?.items || [];
17067
+ const caseStudies = items.filter(
17068
+ (p) => p.tags?.includes(CASE_STUDY_TAG) && hasCustomerSignOff(p)
17058
17069
  );
17059
17070
  setPosts(caseStudies);
17060
- setTotal(result.data?.publicContentPages?.total || 0);
17071
+ const dropped = items.length - caseStudies.length;
17072
+ setTotal(
17073
+ Math.max(0, (result.data?.publicContentPages?.total || 0) - dropped)
17074
+ );
17061
17075
  setError(null);
17062
17076
  } catch {
17063
17077
  setError("load-failed");
@@ -17239,7 +17253,8 @@ function CaseStudyPage(props) {
17239
17253
  setError(result.errors[0]?.message ?? "Failed to load case study");
17240
17254
  return;
17241
17255
  }
17242
- setPost(result.data?.publicContentPage ?? null);
17256
+ const page = result.data?.publicContentPage ?? null;
17257
+ setPost(page && hasCustomerSignOff(page) ? page : null);
17243
17258
  setError(null);
17244
17259
  } catch {
17245
17260
  setError("Failed to load case study");
@@ -17308,6 +17323,7 @@ function CaseStudyPage(props) {
17308
17323
  ] }) });
17309
17324
  }
17310
17325
  const formattedDate = formatDate(post.publishedAt || post.lastUpdated);
17326
+ const approvedDate = formatDate(post.customerSignOff?.signedAt);
17311
17327
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `w-full ${className}`, children: [
17312
17328
  /* @__PURE__ */ jsxRuntime.jsx(ProseStyles, {}),
17313
17329
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -17345,6 +17361,17 @@ function CaseStudyPage(props) {
17345
17361
  ] })
17346
17362
  ] }) : null
17347
17363
  ] }),
17364
+ approvedDate && /* @__PURE__ */ jsxRuntime.jsxs(
17365
+ "p",
17366
+ {
17367
+ className: "-mt-4 mb-8 text-xs text-muted-foreground",
17368
+ "data-cs": "customer-signoff",
17369
+ children: [
17370
+ "Approved for publication by the customer on ",
17371
+ approvedDate
17372
+ ]
17373
+ }
17374
+ ),
17348
17375
  post.thumbnail && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-10 aspect-[16/9] overflow-hidden rounded-xl", children: /* @__PURE__ */ jsxRuntime.jsx(
17349
17376
  "img",
17350
17377
  {
@@ -20637,6 +20664,112 @@ function shouldRenderSiteFooter(pathname) {
20637
20664
  return !isFooterlessPath(pathname);
20638
20665
  }
20639
20666
 
20667
+ // src/comparison/index.ts
20668
+ var DEFAULT_COMPARISON_MAX_AGE_DAYS = 180;
20669
+ var DAY_MS = 24 * 60 * 60 * 1e3;
20670
+ var ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
20671
+ function parseVerifiedAt(value) {
20672
+ if (typeof value !== "string" || !ISO_DATE.test(value)) return null;
20673
+ const date = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
20674
+ if (Number.isNaN(date.getTime())) return null;
20675
+ return date.toISOString().slice(0, 10) === value ? date : null;
20676
+ }
20677
+ function isHttpUrl(value) {
20678
+ try {
20679
+ const url = new URL(value);
20680
+ return url.protocol === "https:" || url.protocol === "http:";
20681
+ } catch {
20682
+ return false;
20683
+ }
20684
+ }
20685
+ function checkComparisonFreshness(claims, options = {}) {
20686
+ const maxAgeDays = options.maxAgeDays ?? DEFAULT_COMPARISON_MAX_AGE_DAYS;
20687
+ const now2 = options.now ?? /* @__PURE__ */ new Date();
20688
+ const issues = [];
20689
+ for (const claim of claims) {
20690
+ const sources = claim.sources ?? [];
20691
+ if (sources.length === 0) {
20692
+ issues.push({
20693
+ path: claim.path,
20694
+ problem: "missing-source",
20695
+ detail: "no source: add { url, verifiedAt } for the vendor page this claim was checked against"
20696
+ });
20697
+ continue;
20698
+ }
20699
+ let newest = null;
20700
+ for (const source of sources) {
20701
+ if (!isHttpUrl(source.url)) {
20702
+ issues.push({
20703
+ path: claim.path,
20704
+ problem: "invalid-url",
20705
+ detail: `source URL is not an http(s) URL: ${JSON.stringify(source.url)}`
20706
+ });
20707
+ }
20708
+ const verified = parseVerifiedAt(source.verifiedAt);
20709
+ if (!verified) {
20710
+ issues.push({
20711
+ path: claim.path,
20712
+ problem: "invalid-date",
20713
+ detail: `verifiedAt must be YYYY-MM-DD, got ${JSON.stringify(source.verifiedAt)}`
20714
+ });
20715
+ continue;
20716
+ }
20717
+ if (verified.getTime() > now2.getTime() + DAY_MS) {
20718
+ issues.push({
20719
+ path: claim.path,
20720
+ problem: "future-date",
20721
+ detail: `verifiedAt ${source.verifiedAt} is in the future`
20722
+ });
20723
+ continue;
20724
+ }
20725
+ if (!newest || verified.getTime() > newest.getTime()) newest = verified;
20726
+ }
20727
+ if (newest) {
20728
+ const ageDays = Math.floor((now2.getTime() - newest.getTime()) / DAY_MS);
20729
+ if (ageDays > maxAgeDays) {
20730
+ issues.push({
20731
+ path: claim.path,
20732
+ problem: "stale",
20733
+ detail: `last verified ${newest.toISOString().slice(0, 10)} (${ageDays} days ago, limit ${maxAgeDays}): re-read the vendor page and update verifiedAt or the claim`
20734
+ });
20735
+ }
20736
+ }
20737
+ }
20738
+ return issues;
20739
+ }
20740
+ function latestVerification(claims) {
20741
+ let latest = null;
20742
+ for (const claim of claims) {
20743
+ for (const source of claim.sources ?? []) {
20744
+ if (parseVerifiedAt(source.verifiedAt) && (!latest || source.verifiedAt > latest)) {
20745
+ latest = source.verifiedAt;
20746
+ }
20747
+ }
20748
+ }
20749
+ return latest;
20750
+ }
20751
+ function oldestVerification(claims) {
20752
+ let oldest = null;
20753
+ for (const claim of claims) {
20754
+ for (const source of claim.sources ?? []) {
20755
+ if (parseVerifiedAt(source.verifiedAt) && (!oldest || source.verifiedAt < oldest)) {
20756
+ oldest = source.verifiedAt;
20757
+ }
20758
+ }
20759
+ }
20760
+ return oldest;
20761
+ }
20762
+ function formatFreshnessReport(issues) {
20763
+ if (issues.length === 0)
20764
+ return "Comparison sources: all claims sourced and fresh.";
20765
+ const lines = issues.map((i) => ` - [${i.problem}] ${i.path}: ${i.detail}`);
20766
+ return [
20767
+ `Comparison sources: ${issues.length} problem(s) found.`,
20768
+ ...lines,
20769
+ "Re-check each claim against the vendor's current page, then update the claim and its verifiedAt."
20770
+ ].join("\n");
20771
+ }
20772
+
20640
20773
  exports.ANSWERS_PATH = ANSWERS_PATH;
20641
20774
  exports.ANSWER_HEADING_SHIFT = ANSWER_HEADING_SHIFT;
20642
20775
  exports.ANSWER_PATH_PREFIX = ANSWER_PATH_PREFIX;
@@ -20659,6 +20792,7 @@ exports.ConceptsPage = ConceptsPage;
20659
20792
  exports.ContactPage = ContactPage;
20660
20793
  exports.ContentRenderer = ContentRenderer;
20661
20794
  exports.ContractsPage = ContractsPage;
20795
+ exports.DEFAULT_COMPARISON_MAX_AGE_DAYS = DEFAULT_COMPARISON_MAX_AGE_DAYS;
20662
20796
  exports.DEFAULT_CONTACT_CATEGORIES = DEFAULT_CONTACT_CATEGORIES;
20663
20797
  exports.DEFAULT_EXPLORE_EXAMPLE_PROMPTS = DEFAULT_EXPLORE_EXAMPLE_PROMPTS;
20664
20798
  exports.DOMAINS_URL = DOMAINS_URL;
@@ -20726,6 +20860,7 @@ exports.answerSpeechText = answerSpeechText;
20726
20860
  exports.answerStrings = answerStrings;
20727
20861
  exports.availablePlanCurrencies = availablePlanCurrencies;
20728
20862
  exports.buttonVariants = buttonVariants;
20863
+ exports.checkComparisonFreshness = checkComparisonFreshness;
20729
20864
  exports.classifyExploreError = classifyExploreError;
20730
20865
  exports.cleanPlanDisplayName = cleanPlanDisplayName;
20731
20866
  exports.cn = cn;
@@ -20742,11 +20877,13 @@ exports.fetchPublicProduct = fetchPublicProduct;
20742
20877
  exports.findLegalDocument = findLegalDocument;
20743
20878
  exports.formatAnswerDate = formatAnswerDate;
20744
20879
  exports.formatDownloads = formatDownloads;
20880
+ exports.formatFreshnessReport = formatFreshnessReport;
20745
20881
  exports.formatLegalDate = formatLegalDate;
20746
20882
  exports.formatPrice = formatPrice2;
20747
20883
  exports.getAnswerBySlug = getAnswerBySlug;
20748
20884
  exports.getConceptBySlug = getConceptBySlug;
20749
20885
  exports.getRecaptchaV3Token = getRecaptchaV3Token;
20886
+ exports.hasCustomerSignOff = hasCustomerSignOff;
20750
20887
  exports.inlineImageOrigins = inlineImageOrigins;
20751
20888
  exports.isFooterlessPath = isFooterlessPath;
20752
20889
  exports.isInlineImageAllowed = isInlineImageAllowed;
@@ -20755,11 +20892,14 @@ exports.isPublishedSourceUrl = isPublishedSourceUrl;
20755
20892
  exports.isSameProductSlug = isSameProductSlug;
20756
20893
  exports.isUuidLike = isUuidLike;
20757
20894
  exports.isValidUrl = isValidUrl;
20895
+ exports.latestVerification = latestVerification;
20758
20896
  exports.legacyProductSlug = legacyProductSlug;
20759
20897
  exports.loadRecaptchaV3 = loadRecaptchaV3;
20760
20898
  exports.looksLikeDeliverableEmail = looksLikeDeliverableEmail;
20761
20899
  exports.normalizeProductSlug = normalizeProductSlug;
20762
20900
  exports.normalizeRoutePath = normalizeRoutePath;
20901
+ exports.oldestVerification = oldestVerification;
20902
+ exports.parseVerifiedAt = parseVerifiedAt;
20763
20903
  exports.planPriceIn = planPriceIn;
20764
20904
  exports.productSlugVariants = productSlugVariants;
20765
20905
  exports.productSlugsByOrigin = productSlugsByOrigin;