@scalemule/news-publication 0.0.81 → 0.0.83

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.
@@ -898,142 +898,34 @@ var PublicationArticleClient = class {
898
898
  "If-Match": String(generation)
899
899
  });
900
900
  }
901
- /** One agent operation over existing owners. Stops at conflicts/unknown upload acknowledgement;
902
- * replay the same operation key to inspect/reuse staged contributions before another transfer.
903
- * Editorial approval and release are always separate explicit operations. */
904
- async submitArticle(input) {
905
- if (input.document.media?.length)
906
- throw new Error(
907
- "Pass file assets separately when creating an article package"
908
- );
909
- const capabilities = await this.options();
910
- if (capabilities.workflow_version !== 1)
911
- throw new Error("Unsupported article workflow version");
912
- if (input.assets?.length && !capabilities.capabilities.managed_private_asset_upload)
913
- throw new Error("Managed article upload is not deployed");
914
- const created = await this.create(
915
- input.operationKey,
916
- input.authorProfileId,
917
- input.document,
918
- input.reviewPolicy
919
- );
920
- try {
921
- const selected = [];
922
- for (const asset of input.assets ?? []) {
923
- let current = await this.get(created.article_id);
924
- const digest3 = Array.from(
925
- new Uint8Array(
926
- await crypto.subtle.digest(
927
- "SHA-256",
928
- await asset.file.arrayBuffer()
929
- )
930
- )
931
- ).map((v) => v.toString(16).padStart(2, "0")).join("");
932
- const existing = current.contributions.find(
933
- (c) => c.kind === "MEDIA" && c.state !== "REJECTED" && c.state !== "WITHDRAWN" && c.payload.sha256 === digest3 && Object.entries(asset.metadata).every(
934
- ([k, v]) => c.payload[k] === v
935
- )
936
- );
937
- const staged = existing?.payload.file_id ? { contribution_id: existing.id, file_id: existing.payload.file_id } : await this.upload(
938
- created.article_id,
939
- current.generation,
940
- asset.file,
941
- asset.filename,
942
- asset.metadata
943
- );
944
- await this.registerPhoto(staged.file_id);
945
- if (input.approveMedia) {
946
- if (!asset.metadata.rights_confirmed || asset.metadata.private_preview_only)
947
- throw new Error("Publication requires confirmed image rights");
948
- if (existing?.state !== "APPROVED") {
949
- current = await this.get(created.article_id);
950
- await this.command(created.article_id, current.generation, {
951
- action: "RESOLVE",
952
- contribution_id: staged.contribution_id,
953
- state: "APPROVED"
954
- });
955
- }
956
- selected.push({
957
- kind: "image",
958
- contribution_id: staged.contribution_id,
959
- file_id: staged.file_id,
960
- caption: asset.metadata.caption,
961
- credit: asset.metadata.credit,
962
- alt: asset.metadata.alt,
963
- position: asset.position
964
- });
965
- }
966
- }
967
- if (selected.length) {
968
- const current = await this.get(created.article_id);
969
- const document = { ...input.document, media: selected };
970
- const saved = current.revisions.find(
971
- (r) => r.id === current.current_revision_id
972
- )?.document;
973
- if (!saved) throw new Error("Current immutable revision is missing");
974
- if (sameDocument(saved, document))
975
- return {
976
- ...created,
977
- revision_id: current.current_revision_id,
978
- generation: current.generation,
979
- editorial_status: current.status,
980
- replayed: true
981
- };
982
- if (!sameDocument(
983
- { ...saved, media: [] },
984
- { ...input.document, media: [] }
985
- ) || saved.media?.length)
986
- throw new ArticleWorkflowError(
987
- "The article was edited after this package began; load its current revision before changing it",
988
- 409,
989
- created.article_id
990
- );
991
- return await this.save(
992
- `${input.operationKey}:assets`,
993
- created.article_id,
994
- current.generation,
995
- document,
996
- "MEDIA_UPDATE"
997
- );
998
- }
999
- return created;
1000
- } catch (error) {
1001
- if (error instanceof ArticleWorkflowError) {
1002
- error.articleId = created.article_id;
1003
- throw error;
1004
- }
1005
- throw new ArticleWorkflowError(
1006
- error instanceof Error ? error.message : "Article package interrupted",
1007
- 0,
1008
- created.article_id
1009
- );
1010
- }
901
+ /** One server-owned submission. No client-side Photo/TTS orchestration and no
902
+ * fallback to a different workflow. A 202 receipt is acceptance, not readiness.
903
+ * Replay the identical manifest/files/key after an uncertain acknowledgement.
904
+ */
905
+ submit(manifest, operationKey, files = [], signal) {
906
+ if (!operationKey) throw new Error("A stable operation key is required");
907
+ if (files.length > 10 || files.some((f) => f.file.size < 1 || f.file.size > 25 * 1024 * 1024) || files.reduce((n, f) => n + f.file.size, 0) > 50 * 1024 * 1024) throw new Error("Supply up to ten photographs, at most 25 MiB each and 50 MiB total");
908
+ const slots = new Set(files.map((f) => f.slot));
909
+ if (slots.size !== files.length || files.length !== (manifest.assets?.length ?? 0) || manifest.assets?.some((a) => !slots.has(a.slot))) throw new Error("Supply one file for each declared asset slot");
910
+ if (!files.length) return this.request("/submissions", "POST", manifest, { "Idempotency-Key": operationKey }, signal);
911
+ const form = new FormData();
912
+ form.append("manifest", JSON.stringify(manifest));
913
+ for (const asset of files) form.append(asset.slot, asset.file, asset.filename);
914
+ return this.request("/submissions", "POST", form, { "Idempotency-Key": operationKey }, signal);
915
+ }
916
+ submission(submissionId, signal) {
917
+ return this.request("/submissions/" + encodeURIComponent(submissionId), "GET", void 0, void 0, signal);
918
+ }
919
+ submitArticle(input) {
920
+ if (input.document.media?.length) throw new Error("Pass file assets separately when creating an article package");
921
+ const assets = (input.assets ?? []).map((a, i) => ({ ...a, slot: "asset_" + String.fromCharCode(97 + i) }));
922
+ return this.submit({
923
+ article: { operation: "create", author_profile_id: input.authorProfileId, document: input.document, review_policy: input.reviewPolicy },
924
+ intent: input.intent ?? "draft",
925
+ approve_media: input.approveMedia,
926
+ assets: assets.map((a) => ({ slot: a.slot, position: a.position, metadata: a.metadata }))
927
+ }, input.operationKey, assets);
1011
928
  }
1012
929
  };
1013
- function sameDocument(a, b) {
1014
- const sorted = (v) => Array.isArray(v) ? v.map(sorted) : v && typeof v === "object" ? Object.fromEntries(Object.keys(v).sort().map((k) => [k, sorted(v[k])])) : v;
1015
- const canonical3 = (d) => [
1016
- d.title,
1017
- d.subtitle || "",
1018
- d.content_markdown,
1019
- d.excerpt ?? null,
1020
- sorted(d.cards ?? null),
1021
- sorted(d.citations ?? null),
1022
- d.section,
1023
- d.community || "",
1024
- d.format || null,
1025
- d.layout ? [d.layout.id, d.layout.version] : null,
1026
- (d.media || []).map((m) => [
1027
- m.kind || "image",
1028
- m.contribution_id,
1029
- m.file_id,
1030
- m.caption,
1031
- m.credit,
1032
- m.alt,
1033
- m.position
1034
- ])
1035
- ];
1036
- return JSON.stringify(canonical3(a)) === JSON.stringify(canonical3(b));
1037
- }
1038
930
 
1039
931
  export { ArticleEditOperationError, ArticlePhotoOperationError, ArticleWorkflowError, PublicationArticleClient, articleEditHtmlMatches, articlePhotoHtmlMatches };
package/dist/index.cjs CHANGED
@@ -829,8 +829,11 @@ var EVENTS = {
829
829
  article_shared: browser("engagement"),
830
830
  promotion_viewed: browser("content"),
831
831
  promotion_clicked: browser("engagement"),
832
+ newsletter_invitation_viewed: browser("content"),
833
+ related_stories_viewed: browser("content"),
832
834
  newsletter_signup_started: browser("engagement"),
833
835
  newsletter_subscription_confirmed: server(),
836
+ experiment_exposed: browser("system"),
834
837
  web_vital_recorded: browser("system"),
835
838
  scroll_depth_reached: browser("content"),
836
839
  navigation_clicked: browser("navigation"),
@@ -3633,6 +3636,198 @@ function articleFormatLabel(format) {
3633
3636
  return format === "opinion" ? "Opinion" : format === "explainer" ? "Explainer" : format === "interview" ? "Interview" : format === "photo_essay" ? "Photo essay" : format === "video_story" ? "Video story" : void 0;
3634
3637
  }
3635
3638
 
3639
+ // src/community-conversion.ts
3640
+ var ARTICLE_CONVERSION_EXPERIMENT = "article_conversion_module";
3641
+ var CELEBRATION_PATTERN = /\b(celebrat(?:e|ion|ions|ing)|wedding|weddings|baby|babies|newborn|quarter[- ]?finals?|milestone|milestones|anniversar(?:y|ies)|graduat(?:e|es|ion|ions)|honor(?:s|ed|ing)?|award(?:s|ed)?|champion|champions|hero(?:es)?|little warrior|birthday)\b/iu;
3642
+ var SPONSORED_MARKER_PATTERN = /\b(sponsored|paid post|partner content|advertorial|promoted|brand studio)\b/iu;
3643
+ function resolveArticleConversionVariant(assignedVariant, queryOverride) {
3644
+ const candidate = (queryOverride ?? assignedVariant ?? "").trim().toLowerCase();
3645
+ if (candidate === "treatment" || candidate === "inline" || candidate === "b") {
3646
+ return "treatment";
3647
+ }
3648
+ return "control";
3649
+ }
3650
+ function classifyStoryInvitationCategory(story) {
3651
+ const combinedText = [
3652
+ story.title,
3653
+ story.excerpt ?? "",
3654
+ story.section ?? "",
3655
+ ...(story.tags ?? []).map((t) => t.name),
3656
+ ...(story.categories ?? []).map((c) => c.name)
3657
+ ].join(" ");
3658
+ if (CELEBRATION_PATTERN.test(combinedText)) {
3659
+ return "celebrations";
3660
+ }
3661
+ const section = resolveSection(story);
3662
+ switch (section) {
3663
+ case "Local businesses":
3664
+ return "local_businesses";
3665
+ case "Schools":
3666
+ return "education";
3667
+ case "Events":
3668
+ return "community_events";
3669
+ case "Civic life":
3670
+ case "Real Estate":
3671
+ case "Health":
3672
+ return "neighborhood_news";
3673
+ default:
3674
+ return "community";
3675
+ }
3676
+ }
3677
+ function contextualCommunityInvitation(story, publication) {
3678
+ const category = classifyStoryInvitationCategory(story);
3679
+ const region = publication.regionLabel.trim() || publication.name.trim();
3680
+ const coverage = publication.coverageText?.trim() || (publication.towns && publication.towns.length > 0 ? publication.towns.slice(0, 3).join(", ") : region);
3681
+ switch (category) {
3682
+ case "celebrations":
3683
+ return {
3684
+ category,
3685
+ eyebrow: `${region} Community`,
3686
+ heading: `Stay connected to ${region}`,
3687
+ subprompt: "Discover more people and milestones worth celebrating.",
3688
+ description: `${region} is full of stories worth celebrating. Discover the people, places, events, and milestones shaping ${coverage}.`,
3689
+ ctaLabel: "Subscribe",
3690
+ relatedHeading: `More stories from ${region}`
3691
+ };
3692
+ case "local_businesses":
3693
+ return {
3694
+ category,
3695
+ eyebrow: `${region} Local Businesses`,
3696
+ heading: `Stay connected to ${region}`,
3697
+ subprompt: "Meet the entrepreneurs building your community.",
3698
+ description: `Discover the founders, independent shops, places, and events shaping ${coverage}.`,
3699
+ ctaLabel: "Subscribe",
3700
+ relatedHeading: `More local stories in ${region}`
3701
+ };
3702
+ case "education":
3703
+ return {
3704
+ category,
3705
+ eyebrow: `${region} Schools & Youth`,
3706
+ heading: `Stay connected to ${region}`,
3707
+ subprompt: "Follow achievements from local students and schools.",
3708
+ description: `Discover the students, educators, neighborhood milestones, and events shaping ${coverage}.`,
3709
+ ctaLabel: "Subscribe",
3710
+ relatedHeading: `More stories from ${region}`
3711
+ };
3712
+ case "community_events":
3713
+ return {
3714
+ category,
3715
+ eyebrow: `${region} Events & Gatherings`,
3716
+ heading: `Stay connected to ${region}`,
3717
+ subprompt: "Find something happening near you this weekend.",
3718
+ description: `Discover the people, places, weekend gatherings, and local stories shaping ${coverage}.`,
3719
+ ctaLabel: "Subscribe",
3720
+ relatedHeading: `Happening around ${region}`
3721
+ };
3722
+ case "neighborhood_news":
3723
+ return {
3724
+ category,
3725
+ eyebrow: `${region} Neighborhood Briefing`,
3726
+ heading: `Stay connected to ${region}`,
3727
+ subprompt: "Stay informed about changes affecting your neighborhood.",
3728
+ description: `Follow the civic decisions, neighborhood projects, people, and events shaping ${coverage}.`,
3729
+ ctaLabel: "Subscribe",
3730
+ relatedHeading: `More from ${region}`
3731
+ };
3732
+ case "community":
3733
+ default:
3734
+ return {
3735
+ category: "community",
3736
+ eyebrow: `${region} Community`,
3737
+ heading: `Stay connected to ${region}`,
3738
+ subprompt: "Discover the people, places, and events shaping your community.",
3739
+ description: `Receive local reporting on the people, places, events, and stories shaping ${coverage}.`,
3740
+ ctaLabel: "Subscribe",
3741
+ relatedHeading: `More stories from ${region}`
3742
+ };
3743
+ }
3744
+ }
3745
+ function isSponsoredStoryItem(item) {
3746
+ const markers = [
3747
+ item.section ?? "",
3748
+ ...(item.tags ?? []).map((t) => `${t.name} ${t.slug}`),
3749
+ ...(item.categories ?? []).map((c) => `${c.name} ${c.slug}`)
3750
+ ].join(" ");
3751
+ return SPONSORED_MARKER_PATTERN.test(markers);
3752
+ }
3753
+ function normalizedCoverIdentity(item) {
3754
+ const raw = item.cover_image_url?.trim();
3755
+ if (!raw) return null;
3756
+ if (item.visual_identity?.version === 1 && /^[a-f0-9]{64}$/u.test(item.visual_identity.key)) {
3757
+ return `pixels:${item.visual_identity.key}`;
3758
+ }
3759
+ try {
3760
+ const url = new URL(raw);
3761
+ if (url.protocol !== "https:" && url.protocol !== "http:") return null;
3762
+ url.hash = "";
3763
+ url.searchParams.sort();
3764
+ return `url:${url.toString()}`;
3765
+ } catch {
3766
+ return null;
3767
+ }
3768
+ }
3769
+ function selectRelevantLocalStories(currentStory, candidates, publication, limit = 3) {
3770
+ if (limit <= 0) return [];
3771
+ const currentSection = resolveSection(currentStory);
3772
+ const pubTowns = Array.isArray(publication.towns) ? publication.towns : [];
3773
+ const currentTowns = new Set(storyTowns(currentStory, pubTowns).map((t) => t.toLowerCase()));
3774
+ const currentTags = /* @__PURE__ */ new Set([
3775
+ ...(currentStory.tags ?? []).map((t) => t.name.toLowerCase()),
3776
+ ...(currentStory.categories ?? []).map((c) => c.name.toLowerCase())
3777
+ ]);
3778
+ const primaryRegion = publication.regionLabel.trim().toLowerCase();
3779
+ const seenIds = /* @__PURE__ */ new Set([currentStory.id]);
3780
+ const seenSlugs = /* @__PURE__ */ new Set([currentStory.slug]);
3781
+ const usedCovers = /* @__PURE__ */ new Set();
3782
+ const currentCover = normalizedCoverIdentity(currentStory);
3783
+ if (currentCover) usedCovers.add(currentCover);
3784
+ const scored = candidates.filter((item) => {
3785
+ if (!item?.id || !item?.slug) return false;
3786
+ if (seenIds.has(item.id) || seenSlugs.has(item.slug)) return false;
3787
+ if (isSponsoredStoryItem(item)) return false;
3788
+ return true;
3789
+ }).map((item) => {
3790
+ let score = 0;
3791
+ const cover = normalizedCoverIdentity(item);
3792
+ if (cover) score += 40;
3793
+ const itemTowns = storyTowns(item, pubTowns).map((t) => t.toLowerCase());
3794
+ if (itemTowns.some((t) => currentTowns.has(t))) {
3795
+ score += 25;
3796
+ } else if (primaryRegion && `${item.title} ${item.excerpt ?? ""}`.toLowerCase().includes(primaryRegion)) {
3797
+ score += 18;
3798
+ }
3799
+ const section = resolveSection(item);
3800
+ if (section === currentSection) {
3801
+ score += 20;
3802
+ } else if (["Community", "Events", "Schools", "Local businesses", "Food & places"].includes(section)) {
3803
+ score += 12;
3804
+ }
3805
+ const itemTags = [
3806
+ ...(item.tags ?? []).map((t) => t.name.toLowerCase()),
3807
+ ...(item.categories ?? []).map((c) => c.name.toLowerCase())
3808
+ ];
3809
+ const sharedTags = itemTags.filter((t) => currentTags.has(t)).length;
3810
+ score += Math.min(sharedTags * 8, 16);
3811
+ if (CELEBRATION_PATTERN.test(`${item.title} ${item.excerpt ?? ""}`)) {
3812
+ score += 6;
3813
+ }
3814
+ const parsedTime = Date.parse(item.published_at);
3815
+ const publishedMs = Number.isFinite(parsedTime) ? parsedTime : 0;
3816
+ return { item: { ...item, section }, cover, score, publishedMs };
3817
+ }).sort((a, b) => b.score - a.score || b.publishedMs - a.publishedMs);
3818
+ const selected = [];
3819
+ for (const entry of scored) {
3820
+ if (selected.length >= limit) break;
3821
+ if (seenIds.has(entry.item.id) || seenSlugs.has(entry.item.slug)) continue;
3822
+ if (entry.cover && usedCovers.has(entry.cover)) continue;
3823
+ seenIds.add(entry.item.id);
3824
+ seenSlugs.add(entry.item.slug);
3825
+ if (entry.cover) usedCovers.add(entry.cover);
3826
+ selected.push(entry.item);
3827
+ }
3828
+ return selected;
3829
+ }
3830
+
3636
3831
  Object.defineProperty(exports, "COOKIE_CONSENT_BACKUP_KEY", {
3637
3832
  enumerable: true,
3638
3833
  get: function () { return compliance.COOKIE_CONSENT_BACKUP_KEY; }
@@ -3731,6 +3926,7 @@ Object.defineProperty(exports, "setNetworkConsentHub", {
3731
3926
  });
3732
3927
  exports.ANALYTICS_COOKIE = ANALYTICS_COOKIE;
3733
3928
  exports.ANALYTICS_VERSION = ANALYTICS_VERSION;
3929
+ exports.ARTICLE_CONVERSION_EXPERIMENT = ARTICLE_CONVERSION_EXPERIMENT;
3734
3930
  exports.ArticleProvenance = ArticleProvenance;
3735
3931
  exports.BROADSHEET_THEME = BROADSHEET_THEME;
3736
3932
  exports.CLASSIFIED_CATEGORIES = CLASSIFIED_CATEGORIES;
@@ -3787,12 +3983,14 @@ exports.canHostMeetup = canHostMeetup;
3787
3983
  exports.categoryLabel = categoryLabel;
3788
3984
  exports.classifiedPath = classifiedPath;
3789
3985
  exports.classifiedSearchPath = classifiedSearchPath;
3986
+ exports.classifyStoryInvitationCategory = classifyStoryInvitationCategory;
3790
3987
  exports.clearTheme = clearTheme;
3791
3988
  exports.clipChars = clipChars;
3792
3989
  exports.communityMapEmbeds = communityMapEmbeds;
3793
3990
  exports.communityPublications = communityPublications;
3794
3991
  exports.composeEdition = composeEdition;
3795
3992
  exports.composeLocalBriefing = composeLocalBriefing;
3993
+ exports.contextualCommunityInvitation = contextualCommunityInvitation;
3796
3994
  exports.dealDollars = dealDollars;
3797
3995
  exports.dealGoPath = dealGoPath;
3798
3996
  exports.dealsFeedPath = dealsFeedPath;
@@ -3818,6 +4016,7 @@ exports.isAdult = isAdult;
3818
4016
  exports.isClassifiedCategory = isClassifiedCategory;
3819
4017
  exports.isEmploymentType = isEmploymentType;
3820
4018
  exports.isSafeActionUrl = isSafeActionUrl;
4019
+ exports.isSponsoredStoryItem = isSponsoredStoryItem;
3821
4020
  exports.jobEmployerBody = jobEmployerBody;
3822
4021
  exports.jobOpeningBody = jobOpeningBody;
3823
4022
  exports.jobPostingBody = jobPostingBody;
@@ -3850,6 +4049,7 @@ exports.readerSources = readerSources;
3850
4049
  exports.recordConsentToCompliance = recordConsentToCompliance;
3851
4050
  exports.referralCode = referralCode;
3852
4051
  exports.renderArticleBlocksToHtml = renderArticleBlocksToHtml;
4052
+ exports.resolveArticleConversionVariant = resolveArticleConversionVariant;
3853
4053
  exports.resolveCoupon = resolveCoupon;
3854
4054
  exports.resolveEffectiveTheme = resolveEffectiveTheme;
3855
4055
  exports.resolveLayout = resolveLayout;
@@ -3863,6 +4063,7 @@ exports.secretsMatch = secretsMatch;
3863
4063
  exports.sectionDefinition = sectionDefinition;
3864
4064
  exports.sectionId = sectionId;
3865
4065
  exports.sectionPath = sectionPath;
4066
+ exports.selectRelevantLocalStories = selectRelevantLocalStories;
3866
4067
  exports.setTheme = setTheme;
3867
4068
  exports.shortDeck = shortDeck;
3868
4069
  exports.shortText = shortText;
package/dist/index.d.cts CHANGED
@@ -624,4 +624,54 @@ interface SponsorshipReport {
624
624
  /** Shared by the newsroom print/export view and the repeatable PDF renderer. */
625
625
  declare function campaignReportHtml(report: SponsorshipReport): string;
626
626
 
627
- export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ArticleProvenance, type Attribution, BROADSHEET_THEME, type BriefingCandidate, type BriefingPreferences, type BriefingReason, type BriefingSection, type BriefingSource, type BriefingVerification, COOKIE_CATEGORIES, CURATED_COMMUNITY_CARDS, CouponOffer, EDITORIAL_SECTION_ORDER, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, type FeaturedDestination, HomepageSelection, LAYOUT_COOKIE, LOCAL_BRIEFING_POLICY_VERSION, type ListeningArticle, type LocalBriefing, type LocalBriefingItem, type LocalBriefingOptions, Measurement, NAVIGATION_GROUPS, NETWORK_NAVIGATION, NEWS_CACHE, type NavigationConfig, type NavigationContext, type NavigationDestination, type NavigationGroup, type NavigationGroupLinks, type NavigationKind, type NavigationLink, type NavigationOverride, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, type ResolvedNavigation, SESSION_IDLE_MS, type SponsorshipReport, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, activeNearby, applyNavigationConfig, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, campaignReportHtml, clearTheme, composeEdition, composeLocalBriefing, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getStoredTheme, getSystemTheme, inventoryCacheOptions, llmsText, matchesStoryTag, measurementConsent, nearbyUpdates, newsArticleJsonLd, normalizedText, orderedSections, publicHttps, publicUrl, publicationJsonLd, publicationListeningCandidates, publicationListeningTrack, publicationMeta, readerSources, recordConsentToCompliance, referralCode, resolveCoupon, resolveEffectiveTheme, resolveLayout, resolveNavigation, resolveSection, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
627
+ declare const ARTICLE_CONVERSION_EXPERIMENT = "article_conversion_module";
628
+ type ArticleConversionVariant = "control" | "treatment";
629
+ type CommunityInvitationCategory = "celebrations" | "local_businesses" | "education" | "neighborhood_news" | "community_events" | "community";
630
+ interface ContextualCommunityInvitation {
631
+ category: CommunityInvitationCategory;
632
+ eyebrow: string;
633
+ heading: string;
634
+ subprompt: string;
635
+ description: string;
636
+ ctaLabel: string;
637
+ relatedHeading: string;
638
+ }
639
+ /**
640
+ * Resolves the `article_conversion_module` experiment variant consistently.
641
+ * Supports query-parameter preview overrides (`?conversion=treatment` or `?conversion=control`)
642
+ * before falling back to the assigned ScaleMule flag variant (`"control"` default).
643
+ */
644
+ declare function resolveArticleConversionVariant(assignedVariant?: string | null, queryOverride?: string | null): ArticleConversionVariant;
645
+ /**
646
+ * Infers the reader-facing community invitation category from a story's section,
647
+ * tags, categories, headline, and excerpt.
648
+ */
649
+ declare function classifyStoryInvitationCategory(story: Pick<NewsItem, "title" | "excerpt" | "section" | "tags" | "categories">): CommunityInvitationCategory;
650
+ /**
651
+ * Builds a publication-aware, category-tailored invitation that connects the reader's
652
+ * reason for visiting a specific article to staying connected with the local community.
653
+ */
654
+ interface CommunityInvitationPublicationContext {
655
+ name: string;
656
+ regionLabel: string;
657
+ coverageText?: string;
658
+ towns?: string[];
659
+ }
660
+ declare function contextualCommunityInvitation(story: Pick<NewsItem, "title" | "excerpt" | "section" | "tags" | "categories">, publication: CommunityInvitationPublicationContext): ContextualCommunityInvitation;
661
+ /**
662
+ * Returns true if a story carries any sponsored, paid, or partner-content markers.
663
+ * Editorial recommendation slots must never mix unlabeled sponsored items.
664
+ */
665
+ declare function isSponsoredStoryItem(item: NewsItem): boolean;
666
+ /**
667
+ * Selects 2–3 relevant local editorial stories for the post-prose Community Conversion Module.
668
+ *
669
+ * Rules:
670
+ * - Never includes the current story or duplicate story IDs/slugs.
671
+ * - Never includes sponsored/paid items (`isSponsoredStoryItem`).
672
+ * - Deduplicates cover images so recommended cards never repeat the current story's cover or each other's cover.
673
+ * - Prioritizes stories with valid cover photographs, shared town/geographic relevance, and community/section affinity.
674
+ */
675
+ declare function selectRelevantLocalStories(currentStory: NewsItem, candidates: NewsItem[], publication: Pick<NewsPublication, "regionLabel" | "towns">, limit?: number): NewsItem[];
676
+
677
+ export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ARTICLE_CONVERSION_EXPERIMENT, type ArticleConversionVariant, ArticleProvenance, type Attribution, BROADSHEET_THEME, type BriefingCandidate, type BriefingPreferences, type BriefingReason, type BriefingSection, type BriefingSource, type BriefingVerification, COOKIE_CATEGORIES, CURATED_COMMUNITY_CARDS, type CommunityInvitationCategory, type ContextualCommunityInvitation, CouponOffer, EDITORIAL_SECTION_ORDER, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, type FeaturedDestination, HomepageSelection, LAYOUT_COOKIE, LOCAL_BRIEFING_POLICY_VERSION, type ListeningArticle, type LocalBriefing, type LocalBriefingItem, type LocalBriefingOptions, Measurement, NAVIGATION_GROUPS, NETWORK_NAVIGATION, NEWS_CACHE, type NavigationConfig, type NavigationContext, type NavigationDestination, type NavigationGroup, type NavigationGroupLinks, type NavigationKind, type NavigationLink, type NavigationOverride, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, type ResolvedNavigation, SESSION_IDLE_MS, type SponsorshipReport, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, activeNearby, applyNavigationConfig, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, campaignReportHtml, classifyStoryInvitationCategory, clearTheme, composeEdition, composeLocalBriefing, contextualCommunityInvitation, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getStoredTheme, getSystemTheme, inventoryCacheOptions, isSponsoredStoryItem, llmsText, matchesStoryTag, measurementConsent, nearbyUpdates, newsArticleJsonLd, normalizedText, orderedSections, publicHttps, publicUrl, publicationJsonLd, publicationListeningCandidates, publicationListeningTrack, publicationMeta, readerSources, recordConsentToCompliance, referralCode, resolveArticleConversionVariant, resolveCoupon, resolveEffectiveTheme, resolveLayout, resolveNavigation, resolveSection, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, selectRelevantLocalStories, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
package/dist/index.d.ts CHANGED
@@ -624,4 +624,54 @@ interface SponsorshipReport {
624
624
  /** Shared by the newsroom print/export view and the repeatable PDF renderer. */
625
625
  declare function campaignReportHtml(report: SponsorshipReport): string;
626
626
 
627
- export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ArticleProvenance, type Attribution, BROADSHEET_THEME, type BriefingCandidate, type BriefingPreferences, type BriefingReason, type BriefingSection, type BriefingSource, type BriefingVerification, COOKIE_CATEGORIES, CURATED_COMMUNITY_CARDS, CouponOffer, EDITORIAL_SECTION_ORDER, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, type FeaturedDestination, HomepageSelection, LAYOUT_COOKIE, LOCAL_BRIEFING_POLICY_VERSION, type ListeningArticle, type LocalBriefing, type LocalBriefingItem, type LocalBriefingOptions, Measurement, NAVIGATION_GROUPS, NETWORK_NAVIGATION, NEWS_CACHE, type NavigationConfig, type NavigationContext, type NavigationDestination, type NavigationGroup, type NavigationGroupLinks, type NavigationKind, type NavigationLink, type NavigationOverride, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, type ResolvedNavigation, SESSION_IDLE_MS, type SponsorshipReport, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, activeNearby, applyNavigationConfig, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, campaignReportHtml, clearTheme, composeEdition, composeLocalBriefing, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getStoredTheme, getSystemTheme, inventoryCacheOptions, llmsText, matchesStoryTag, measurementConsent, nearbyUpdates, newsArticleJsonLd, normalizedText, orderedSections, publicHttps, publicUrl, publicationJsonLd, publicationListeningCandidates, publicationListeningTrack, publicationMeta, readerSources, recordConsentToCompliance, referralCode, resolveCoupon, resolveEffectiveTheme, resolveLayout, resolveNavigation, resolveSection, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
627
+ declare const ARTICLE_CONVERSION_EXPERIMENT = "article_conversion_module";
628
+ type ArticleConversionVariant = "control" | "treatment";
629
+ type CommunityInvitationCategory = "celebrations" | "local_businesses" | "education" | "neighborhood_news" | "community_events" | "community";
630
+ interface ContextualCommunityInvitation {
631
+ category: CommunityInvitationCategory;
632
+ eyebrow: string;
633
+ heading: string;
634
+ subprompt: string;
635
+ description: string;
636
+ ctaLabel: string;
637
+ relatedHeading: string;
638
+ }
639
+ /**
640
+ * Resolves the `article_conversion_module` experiment variant consistently.
641
+ * Supports query-parameter preview overrides (`?conversion=treatment` or `?conversion=control`)
642
+ * before falling back to the assigned ScaleMule flag variant (`"control"` default).
643
+ */
644
+ declare function resolveArticleConversionVariant(assignedVariant?: string | null, queryOverride?: string | null): ArticleConversionVariant;
645
+ /**
646
+ * Infers the reader-facing community invitation category from a story's section,
647
+ * tags, categories, headline, and excerpt.
648
+ */
649
+ declare function classifyStoryInvitationCategory(story: Pick<NewsItem, "title" | "excerpt" | "section" | "tags" | "categories">): CommunityInvitationCategory;
650
+ /**
651
+ * Builds a publication-aware, category-tailored invitation that connects the reader's
652
+ * reason for visiting a specific article to staying connected with the local community.
653
+ */
654
+ interface CommunityInvitationPublicationContext {
655
+ name: string;
656
+ regionLabel: string;
657
+ coverageText?: string;
658
+ towns?: string[];
659
+ }
660
+ declare function contextualCommunityInvitation(story: Pick<NewsItem, "title" | "excerpt" | "section" | "tags" | "categories">, publication: CommunityInvitationPublicationContext): ContextualCommunityInvitation;
661
+ /**
662
+ * Returns true if a story carries any sponsored, paid, or partner-content markers.
663
+ * Editorial recommendation slots must never mix unlabeled sponsored items.
664
+ */
665
+ declare function isSponsoredStoryItem(item: NewsItem): boolean;
666
+ /**
667
+ * Selects 2–3 relevant local editorial stories for the post-prose Community Conversion Module.
668
+ *
669
+ * Rules:
670
+ * - Never includes the current story or duplicate story IDs/slugs.
671
+ * - Never includes sponsored/paid items (`isSponsoredStoryItem`).
672
+ * - Deduplicates cover images so recommended cards never repeat the current story's cover or each other's cover.
673
+ * - Prioritizes stories with valid cover photographs, shared town/geographic relevance, and community/section affinity.
674
+ */
675
+ declare function selectRelevantLocalStories(currentStory: NewsItem, candidates: NewsItem[], publication: Pick<NewsPublication, "regionLabel" | "towns">, limit?: number): NewsItem[];
676
+
677
+ export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ARTICLE_CONVERSION_EXPERIMENT, type ArticleConversionVariant, ArticleProvenance, type Attribution, BROADSHEET_THEME, type BriefingCandidate, type BriefingPreferences, type BriefingReason, type BriefingSection, type BriefingSource, type BriefingVerification, COOKIE_CATEGORIES, CURATED_COMMUNITY_CARDS, type CommunityInvitationCategory, type ContextualCommunityInvitation, CouponOffer, EDITORIAL_SECTION_ORDER, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, type FeaturedDestination, HomepageSelection, LAYOUT_COOKIE, LOCAL_BRIEFING_POLICY_VERSION, type ListeningArticle, type LocalBriefing, type LocalBriefingItem, type LocalBriefingOptions, Measurement, NAVIGATION_GROUPS, NETWORK_NAVIGATION, NEWS_CACHE, type NavigationConfig, type NavigationContext, type NavigationDestination, type NavigationGroup, type NavigationGroupLinks, type NavigationKind, type NavigationLink, type NavigationOverride, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, type ResolvedNavigation, SESSION_IDLE_MS, type SponsorshipReport, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, activeNearby, applyNavigationConfig, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, campaignReportHtml, classifyStoryInvitationCategory, clearTheme, composeEdition, composeLocalBriefing, contextualCommunityInvitation, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getStoredTheme, getSystemTheme, inventoryCacheOptions, isSponsoredStoryItem, llmsText, matchesStoryTag, measurementConsent, nearbyUpdates, newsArticleJsonLd, normalizedText, orderedSections, publicHttps, publicUrl, publicationJsonLd, publicationListeningCandidates, publicationListeningTrack, publicationMeta, readerSources, recordConsentToCompliance, referralCode, resolveArticleConversionVariant, resolveCoupon, resolveEffectiveTheme, resolveLayout, resolveNavigation, resolveSection, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, selectRelevantLocalStories, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };