@scalemule/news-publication 0.0.17 → 0.0.19

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.
package/README.md CHANGED
@@ -72,3 +72,16 @@ The React front page still lives in the Walnut Creek Times repository. This pack
72
72
  ## Versioning
73
73
 
74
74
  Patch only (`0.0.x` or `0.1.x`) until a stable release is explicitly approved. Publishing is manual: `npm publish` from this directory.
75
+
76
+
77
+ ### Reporting credit and sources
78
+
79
+ `ArticleProvenance` renders the public reporting projection: analysis/commentary labels, network and wire credit, and up to five deduplicated source links. It accepts `NewsArticle.metadata` and never renders internal claim mappings, reporter notes or source artifacts. Syndicated commentary keeps its canonical commentary label.
80
+
81
+ ```tsx
82
+ import { ArticleProvenance } from '@scalemule/news-publication'
83
+
84
+ <ArticleProvenance metadata={article.metadata} />
85
+ ```
86
+
87
+ The component inherits the publication's typography. Optional styling hooks are `article-provenance` and `article-provenance__label`. `readerSources()` is available when an existing publication component owns source-list rendering.
package/dist/index.cjs CHANGED
@@ -320,11 +320,16 @@ function newsArticleJsonLd(publication, article, canonical, authorName) {
320
320
  headline: article.title,
321
321
  description: article.excerpt ?? void 0,
322
322
  datePublished: article.published_at,
323
- dateModified: article.updated_at,
323
+ // updated_at also changes for media/operational maintenance. A trusted
324
+ // editorial modification timestamp is required before emitting dateModified.
324
325
  image: image ? [image] : void 0,
325
- mainEntityOfPage: canonicalUrl,
326
+ mainEntityOfPage: url,
326
327
  url,
327
- author: { "@type": "Organization", name: authorName || publication.name },
328
+ ...canonicalUrl !== url ? { isBasedOn: canonicalUrl } : {},
329
+ articleSection: article.section || void 0,
330
+ // The publication byline is known to be an organization. Do not infer a
331
+ // person's identity or classify an untyped external byline as a newsroom.
332
+ author: { ...!authorName || authorName === publication.name ? { "@type": "Organization" } : {}, name: authorName || publication.name },
328
333
  publisher: { "@type": "NewsMediaOrganization", name: publication.name, url: canonical }
329
334
  };
330
335
  }
@@ -359,17 +364,17 @@ function articleMeta(publication, article, canonical, indexable) {
359
364
  const canonicalUrl = publicHttps(article.canonical_url) ?? url;
360
365
  const image = publicHttps(article.og_image_url) ?? publicHttps(article.cover_image_url);
361
366
  return {
362
- title: article.seo_title ?? article.title,
367
+ title: article.title,
363
368
  description: article.seo_description ?? article.excerpt ?? publication.description,
364
369
  canonical: canonicalUrl,
365
370
  indexable: indexable && !article.no_index,
366
371
  openGraph: {
367
372
  type: "article",
373
+ siteName: publication.name,
368
374
  title: article.title,
369
375
  description: article.excerpt ?? void 0,
370
376
  url,
371
377
  publishedTime: article.published_at,
372
- modifiedTime: article.updated_at,
373
378
  images: image ? [image] : []
374
379
  }
375
380
  };
@@ -1759,6 +1764,56 @@ function LocalNetworkBar({ currentSlug, className = "", style }) {
1759
1764
  }
1760
1765
  );
1761
1766
  }
1767
+ function ArticleProvenance({ metadata }) {
1768
+ const origin = metadata?.reporting?.canonical_origin || metadata?.editorial_origin;
1769
+ const reporting = metadata?.reporting;
1770
+ const label = origin === "COMMENTARY" ? "Commentary" : origin === "ANALYSIS" ? "Analysis" : null;
1771
+ const sources = readerSources(metadata?.citations);
1772
+ const related = readerSources(reporting?.related_links?.map((url) => ({ url })));
1773
+ const credit = reporting?.credit?.trim();
1774
+ const byline = reporting?.original_byline?.trim();
1775
+ const wire = reporting?.wire_credit?.trim();
1776
+ if (!label && !credit && !byline && !wire && !sources.length && !related.length) return null;
1777
+ return /* @__PURE__ */ jsxRuntime.jsxs("aside", { "aria-label": "About this reporting", className: "article-provenance", children: [
1778
+ label && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "article-provenance__label", children: label }),
1779
+ (credit || byline) && /* @__PURE__ */ jsxRuntime.jsx("p", { children: [credit, byline].filter(Boolean).join(" \xB7 ") }),
1780
+ wire && /* @__PURE__ */ jsxRuntime.jsx("p", { children: wire }),
1781
+ !!sources.length && /* @__PURE__ */ jsxRuntime.jsxs("details", { children: [
1782
+ /* @__PURE__ */ jsxRuntime.jsx("summary", { children: "Sources" }),
1783
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { children: sources.map((source) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx("a", { href: source.url, rel: "noopener noreferrer", target: "_blank", children: source.title || source.publisher || new URL(source.url).hostname }) }, source.url)) })
1784
+ ] }),
1785
+ !!related.length && /* @__PURE__ */ jsxRuntime.jsxs("details", { children: [
1786
+ /* @__PURE__ */ jsxRuntime.jsx("summary", { children: "Related reporting" }),
1787
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { children: related.map((link) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: link.url, rel: "noopener noreferrer", children: [
1788
+ new URL(link.url).hostname,
1789
+ new URL(link.url).pathname
1790
+ ] }) }, link.url)) })
1791
+ ] })
1792
+ ] });
1793
+ }
1794
+ function readerSources(input) {
1795
+ if (!Array.isArray(input)) return [];
1796
+ const seen = /* @__PURE__ */ new Set();
1797
+ const output = [];
1798
+ for (const value of input) {
1799
+ if (!value || typeof value.url !== "string") continue;
1800
+ try {
1801
+ const url = new URL(value.url);
1802
+ if (url.protocol !== "https:" || url.username || url.password || seen.has(url.href)) continue;
1803
+ seen.add(url.href);
1804
+ const text = (raw, limit) => {
1805
+ if (typeof raw !== "string") return void 0;
1806
+ const trimmed = raw.trim();
1807
+ return trimmed ? Array.from(trimmed).slice(0, limit).join("") : void 0;
1808
+ };
1809
+ output.push({ url: url.href, title: text(value.title, 500), publisher: text(value.publisher, 255) });
1810
+ if (output.length === 5) break;
1811
+ } catch {
1812
+ continue;
1813
+ }
1814
+ }
1815
+ return output;
1816
+ }
1762
1817
 
1763
1818
  Object.defineProperty(exports, "COOKIE_CONSENT_BACKUP_KEY", {
1764
1819
  enumerable: true,
@@ -1846,6 +1901,7 @@ Object.defineProperty(exports, "setNetworkConsentHub", {
1846
1901
  });
1847
1902
  exports.ANALYTICS_COOKIE = ANALYTICS_COOKIE;
1848
1903
  exports.ANALYTICS_VERSION = ANALYTICS_VERSION;
1904
+ exports.ArticleProvenance = ArticleProvenance;
1849
1905
  exports.BROADSHEET_THEME = BROADSHEET_THEME;
1850
1906
  exports.CLASSIFIED_CATEGORIES = CLASSIFIED_CATEGORIES;
1851
1907
  exports.COOKIE_CATEGORIES = COOKIE_CATEGORIES;
@@ -1930,6 +1986,7 @@ exports.publicationFromSnapshot = publicationFromSnapshot;
1930
1986
  exports.publicationJsonLd = publicationJsonLd;
1931
1987
  exports.publicationMeta = publicationMeta;
1932
1988
  exports.publicationRecord = publicationRecord;
1989
+ exports.readerSources = readerSources;
1933
1990
  exports.recordConsentToCompliance = recordConsentToCompliance;
1934
1991
  exports.referralCode = referralCode;
1935
1992
  exports.resolveCoupon = resolveCoupon;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { b as PublicationTheme, c as PublicationInput, N as NewsPublication, F as FeatureName, H as HomepageSelection, d as NearbyUpdate, e as NewsItem, E as EditorialSection, f as NewsArticle, C as CouponOffer } from './snapshot-sHgQHuAF.cjs';
2
- export { A as ArticleCitation, g as CategoryRef, h as CouponSettings, i as CoverImageMetadata, j as FEATURE_NAMES, k as FeatureFlags, l as NewsFeed, m as NewsLayoutId, n as PublicationModules, o as PublicationRecord, P as PublicationSnapshot, R as ReferralSettings, S as SnapshotHooks, p as StoryDisplay, q as SyncSource, a as SyncedPublication, T as TagRef, r as acceptRemoteRecord, s as acceptSnapshot, t as parseSnapshot, u as publicationFromSnapshot, v as publicationRecord, w as snapshotFromRecord, x as syncPublication } from './snapshot-sHgQHuAF.cjs';
1
+ import { b as PublicationTheme, c as PublicationInput, N as NewsPublication, F as FeatureName, H as HomepageSelection, d as NearbyUpdate, e as NewsItem, E as EditorialSection, f as NewsArticle, C as CouponOffer } from './snapshot-OZFP3-Up.cjs';
2
+ export { A as ArticleCitation, g as CategoryRef, h as CouponSettings, i as CoverImageMetadata, j as FEATURE_NAMES, k as FeatureFlags, l as NewsFeed, m as NewsLayoutId, n as PublicationModules, o as PublicationRecord, P as PublicationSnapshot, R as ReferralSettings, S as SnapshotHooks, p as StoryDisplay, q as SyncSource, a as SyncedPublication, T as TagRef, r as acceptRemoteRecord, s as acceptSnapshot, t as parseSnapshot, u as publicationFromSnapshot, v as publicationRecord, w as snapshotFromRecord, x as syncPublication } from './snapshot-OZFP3-Up.cjs';
3
3
  export { M as MEET_ACTIVITIES, b as MeetActivity, c as MeetupInput, a as PreparedMeetup, P as PublicPlace, d as activityLabel, e as canHostMeetup, i as isAdult, p as prepareMeetup, s as screenMessage } from './meet-RWmfj3I3.cjs';
4
4
  export { b as CLASSIFIED_CATEGORIES, c as ClassifiedCategory, C as ClassifiedListing, E as EmploymentType, J as JOB_EMPLOYMENT_TYPES, d as PreparedClassified, e as PreparedJob, P as PublicDeal, f as PublicDealOffer, a as PublicJob, g as categoryLabel, h as classifiedPath, i as classifiedSearchPath, j as clipChars, k as dealDollars, l as dealGoPath, m as dealsFeedPath, n as employmentLabel, o as isClassifiedCategory, p as isEmploymentType, q as jobEmployerBody, r as jobOpeningBody, s as jobPostingBody, t as prepareClassified, u as prepareJobPost, v as publicJobPath, w as publicJobsPath } from './marketplace-29i5w4LS.cjs';
5
5
  import { CookieCategoryDefinition, CookieConsentRecord } from '@scalemule/compliance';
@@ -9,7 +9,7 @@ export { CookieSettingsView, CookieSettingsViewProps } from './cookie-settings-v
9
9
  export { ThemeToggle, ThemeToggleProps } from './theme-toggle.cjs';
10
10
  export { PublicationNetworkItem, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, listedPublications, localPublications } from './network.cjs';
11
11
  export { LocalNetworkBar, LocalNetworkBarProps } from './network-bar.cjs';
12
- import 'react';
12
+ import * as react from 'react';
13
13
 
14
14
  /** Walnut Creek Times broadsheet colors. A paper overrides only what it changes. */
15
15
  declare const BROADSHEET_THEME: PublicationTheme;
@@ -146,26 +146,27 @@ declare function storyDate(value: string): string | null;
146
146
  /** https URL with no userinfo. Anything else is dropped from public markup. */
147
147
  declare function publicHttps(value?: string | null): string | undefined;
148
148
  declare function articleUrl(canonical: string, slug: string): string;
149
- /** Same NewsArticle graph Walnut Creek Times emits on an indexable story. */
149
+ /** Describe the local edition while retaining the designated reporting source. */
150
150
  declare function newsArticleJsonLd(publication: NewsPublication, article: NewsArticle, canonical: string, authorName?: string | null): {
151
- "@context": string;
152
- "@type": string;
153
- headline: string;
154
- description: string | undefined;
155
- datePublished: string;
156
- dateModified: string;
157
- image: string[] | undefined;
158
- mainEntityOfPage: string;
159
- url: string;
151
+ articleSection: string | undefined;
160
152
  author: {
161
- "@type": string;
162
153
  name: string;
154
+ "@type"?: string | undefined;
163
155
  };
164
156
  publisher: {
165
157
  "@type": string;
166
158
  name: string;
167
159
  url: string;
168
160
  };
161
+ isBasedOn?: string | undefined;
162
+ "@context": string;
163
+ "@type": string;
164
+ headline: string;
165
+ description: string | undefined;
166
+ datePublished: string;
167
+ image: string[] | undefined;
168
+ mainEntityOfPage: string;
169
+ url: string;
169
170
  };
170
171
  declare function publicationJsonLd(publication: NewsPublication, canonical: string): {
171
172
  "@context": string;
@@ -199,11 +200,11 @@ declare function articleMeta(publication: NewsPublication, article: NewsArticle,
199
200
  indexable: boolean;
200
201
  openGraph: {
201
202
  type: "article";
203
+ siteName: string;
202
204
  title: string;
203
205
  description: string | undefined;
204
206
  url: string;
205
207
  publishedTime: string;
206
- modifiedTime: string;
207
208
  images: string[];
208
209
  };
209
210
  };
@@ -292,4 +293,12 @@ declare function subscribeTheme(listener: () => void): () => void;
292
293
  */
293
294
  declare function themeInitScript(): string;
294
295
 
295
- export { ANALYTICS_COOKIE, ANALYTICS_VERSION, type Attribution, BROADSHEET_THEME, COOKIE_CATEGORIES, CouponOffer, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, HomepageSelection, LAYOUT_COOKIE, NEWS_CACHE, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, SESSION_IDLE_MS, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, activeNearby, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, clearTheme, composeEdition, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getStoredTheme, getSystemTheme, inventoryCacheOptions, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, publicHttps, publicUrl, publicationJsonLd, publicationMeta, recordConsentToCompliance, referralCode, resolveCoupon, resolveEffectiveTheme, resolveLayout, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
296
+ /** A reader projection, never an internal reporting package. */
297
+ declare function ArticleProvenance({ metadata }: Pick<NewsArticle, 'metadata'>): react.JSX.Element | null;
298
+ declare function readerSources(input: unknown): {
299
+ url: string;
300
+ title?: string;
301
+ publisher?: string;
302
+ }[];
303
+
304
+ export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ArticleProvenance, type Attribution, BROADSHEET_THEME, COOKIE_CATEGORIES, CouponOffer, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, HomepageSelection, LAYOUT_COOKIE, NEWS_CACHE, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, SESSION_IDLE_MS, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, activeNearby, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, clearTheme, composeEdition, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getStoredTheme, getSystemTheme, inventoryCacheOptions, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, publicHttps, publicUrl, publicationJsonLd, publicationMeta, readerSources, recordConsentToCompliance, referralCode, resolveCoupon, resolveEffectiveTheme, resolveLayout, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { b as PublicationTheme, c as PublicationInput, N as NewsPublication, F as FeatureName, H as HomepageSelection, d as NearbyUpdate, e as NewsItem, E as EditorialSection, f as NewsArticle, C as CouponOffer } from './snapshot-sHgQHuAF.js';
2
- export { A as ArticleCitation, g as CategoryRef, h as CouponSettings, i as CoverImageMetadata, j as FEATURE_NAMES, k as FeatureFlags, l as NewsFeed, m as NewsLayoutId, n as PublicationModules, o as PublicationRecord, P as PublicationSnapshot, R as ReferralSettings, S as SnapshotHooks, p as StoryDisplay, q as SyncSource, a as SyncedPublication, T as TagRef, r as acceptRemoteRecord, s as acceptSnapshot, t as parseSnapshot, u as publicationFromSnapshot, v as publicationRecord, w as snapshotFromRecord, x as syncPublication } from './snapshot-sHgQHuAF.js';
1
+ import { b as PublicationTheme, c as PublicationInput, N as NewsPublication, F as FeatureName, H as HomepageSelection, d as NearbyUpdate, e as NewsItem, E as EditorialSection, f as NewsArticle, C as CouponOffer } from './snapshot-OZFP3-Up.js';
2
+ export { A as ArticleCitation, g as CategoryRef, h as CouponSettings, i as CoverImageMetadata, j as FEATURE_NAMES, k as FeatureFlags, l as NewsFeed, m as NewsLayoutId, n as PublicationModules, o as PublicationRecord, P as PublicationSnapshot, R as ReferralSettings, S as SnapshotHooks, p as StoryDisplay, q as SyncSource, a as SyncedPublication, T as TagRef, r as acceptRemoteRecord, s as acceptSnapshot, t as parseSnapshot, u as publicationFromSnapshot, v as publicationRecord, w as snapshotFromRecord, x as syncPublication } from './snapshot-OZFP3-Up.js';
3
3
  export { M as MEET_ACTIVITIES, b as MeetActivity, c as MeetupInput, a as PreparedMeetup, P as PublicPlace, d as activityLabel, e as canHostMeetup, i as isAdult, p as prepareMeetup, s as screenMessage } from './meet-RWmfj3I3.js';
4
4
  export { b as CLASSIFIED_CATEGORIES, c as ClassifiedCategory, C as ClassifiedListing, E as EmploymentType, J as JOB_EMPLOYMENT_TYPES, d as PreparedClassified, e as PreparedJob, P as PublicDeal, f as PublicDealOffer, a as PublicJob, g as categoryLabel, h as classifiedPath, i as classifiedSearchPath, j as clipChars, k as dealDollars, l as dealGoPath, m as dealsFeedPath, n as employmentLabel, o as isClassifiedCategory, p as isEmploymentType, q as jobEmployerBody, r as jobOpeningBody, s as jobPostingBody, t as prepareClassified, u as prepareJobPost, v as publicJobPath, w as publicJobsPath } from './marketplace-29i5w4LS.js';
5
5
  import { CookieCategoryDefinition, CookieConsentRecord } from '@scalemule/compliance';
@@ -9,7 +9,7 @@ export { CookieSettingsView, CookieSettingsViewProps } from './cookie-settings-v
9
9
  export { ThemeToggle, ThemeToggleProps } from './theme-toggle.js';
10
10
  export { PublicationNetworkItem, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, listedPublications, localPublications } from './network.js';
11
11
  export { LocalNetworkBar, LocalNetworkBarProps } from './network-bar.js';
12
- import 'react';
12
+ import * as react from 'react';
13
13
 
14
14
  /** Walnut Creek Times broadsheet colors. A paper overrides only what it changes. */
15
15
  declare const BROADSHEET_THEME: PublicationTheme;
@@ -146,26 +146,27 @@ declare function storyDate(value: string): string | null;
146
146
  /** https URL with no userinfo. Anything else is dropped from public markup. */
147
147
  declare function publicHttps(value?: string | null): string | undefined;
148
148
  declare function articleUrl(canonical: string, slug: string): string;
149
- /** Same NewsArticle graph Walnut Creek Times emits on an indexable story. */
149
+ /** Describe the local edition while retaining the designated reporting source. */
150
150
  declare function newsArticleJsonLd(publication: NewsPublication, article: NewsArticle, canonical: string, authorName?: string | null): {
151
- "@context": string;
152
- "@type": string;
153
- headline: string;
154
- description: string | undefined;
155
- datePublished: string;
156
- dateModified: string;
157
- image: string[] | undefined;
158
- mainEntityOfPage: string;
159
- url: string;
151
+ articleSection: string | undefined;
160
152
  author: {
161
- "@type": string;
162
153
  name: string;
154
+ "@type"?: string | undefined;
163
155
  };
164
156
  publisher: {
165
157
  "@type": string;
166
158
  name: string;
167
159
  url: string;
168
160
  };
161
+ isBasedOn?: string | undefined;
162
+ "@context": string;
163
+ "@type": string;
164
+ headline: string;
165
+ description: string | undefined;
166
+ datePublished: string;
167
+ image: string[] | undefined;
168
+ mainEntityOfPage: string;
169
+ url: string;
169
170
  };
170
171
  declare function publicationJsonLd(publication: NewsPublication, canonical: string): {
171
172
  "@context": string;
@@ -199,11 +200,11 @@ declare function articleMeta(publication: NewsPublication, article: NewsArticle,
199
200
  indexable: boolean;
200
201
  openGraph: {
201
202
  type: "article";
203
+ siteName: string;
202
204
  title: string;
203
205
  description: string | undefined;
204
206
  url: string;
205
207
  publishedTime: string;
206
- modifiedTime: string;
207
208
  images: string[];
208
209
  };
209
210
  };
@@ -292,4 +293,12 @@ declare function subscribeTheme(listener: () => void): () => void;
292
293
  */
293
294
  declare function themeInitScript(): string;
294
295
 
295
- export { ANALYTICS_COOKIE, ANALYTICS_VERSION, type Attribution, BROADSHEET_THEME, COOKIE_CATEGORIES, CouponOffer, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, HomepageSelection, LAYOUT_COOKIE, NEWS_CACHE, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, SESSION_IDLE_MS, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, activeNearby, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, clearTheme, composeEdition, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getStoredTheme, getSystemTheme, inventoryCacheOptions, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, publicHttps, publicUrl, publicationJsonLd, publicationMeta, recordConsentToCompliance, referralCode, resolveCoupon, resolveEffectiveTheme, resolveLayout, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
296
+ /** A reader projection, never an internal reporting package. */
297
+ declare function ArticleProvenance({ metadata }: Pick<NewsArticle, 'metadata'>): react.JSX.Element | null;
298
+ declare function readerSources(input: unknown): {
299
+ url: string;
300
+ title?: string;
301
+ publisher?: string;
302
+ }[];
303
+
304
+ export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ArticleProvenance, type Attribution, BROADSHEET_THEME, COOKIE_CATEGORIES, CouponOffer, EVENTS, EditorialSection, type EffectiveTheme, FeatureName, HomepageSelection, LAYOUT_COOKIE, NEWS_CACHE, NearbyUpdate, NewsArticle, NewsError, NewsItem, NewsPublication, PublicationInput, PublicationTheme, SESSION_IDLE_MS, THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, type ThemeMode, UTM_FIELDS, UUID, activeNearby, applyTheme, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, clearTheme, composeEdition, defaultRevalidateTargets, definePublication, displayHeadline, edgeCacheControl, editorialSections, featureEnabled, feedCacheKey, feedCacheOptions, filterNews, getStoredTheme, getSystemTheme, inventoryCacheOptions, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, publicHttps, publicUrl, publicationJsonLd, publicationMeta, readerSources, recordConsentToCompliance, referralCode, resolveCoupon, resolveEffectiveTheme, resolveLayout, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, setTheme, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, subscribeTheme, themeInitScript, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ export { MEET_ACTIVITIES, activityLabel, canHostMeetup, isAdult, prepareMeetup,
7
7
  export { CookieConsentBanner } from './chunk-HTMIFWNA.js';
8
8
  export { CookieSettingsView } from './chunk-3MFKPHO3.js';
9
9
  export { COOKIE_CATEGORIES, COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, DEFAULT_CONSENT_HUB_URL, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EU_EEA_UK_COUNTRIES, NETWORK_CONSENT_STORAGE_KEY, acceptAllCookies, checkUrlConsentBridge, clearCookieConsent, communityPublications, decorateNetworkUrl, findPublicationByHost, findPublicationBySlug, getCookieConsent, getNetworkPublicationUrl, hasConsented, isCategoryAllowed, isGeoConsentRequired, listedPublications, localPublications, queryNetworkConsentHub, recordConsentToCompliance, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub } from './chunk-2IYYEFLD.js';
10
+ import { jsxs, jsx } from 'react/jsx-runtime';
10
11
 
11
12
  // src/preset.ts
12
13
  var walnutCreekTimes = definePublication({
@@ -255,11 +256,16 @@ function newsArticleJsonLd(publication, article, canonical, authorName) {
255
256
  headline: article.title,
256
257
  description: article.excerpt ?? void 0,
257
258
  datePublished: article.published_at,
258
- dateModified: article.updated_at,
259
+ // updated_at also changes for media/operational maintenance. A trusted
260
+ // editorial modification timestamp is required before emitting dateModified.
259
261
  image: image ? [image] : void 0,
260
- mainEntityOfPage: canonicalUrl,
262
+ mainEntityOfPage: url,
261
263
  url,
262
- author: { "@type": "Organization", name: authorName || publication.name },
264
+ ...canonicalUrl !== url ? { isBasedOn: canonicalUrl } : {},
265
+ articleSection: article.section || void 0,
266
+ // The publication byline is known to be an organization. Do not infer a
267
+ // person's identity or classify an untyped external byline as a newsroom.
268
+ author: { ...!authorName || authorName === publication.name ? { "@type": "Organization" } : {}, name: authorName || publication.name },
263
269
  publisher: { "@type": "NewsMediaOrganization", name: publication.name, url: canonical }
264
270
  };
265
271
  }
@@ -294,17 +300,17 @@ function articleMeta(publication, article, canonical, indexable) {
294
300
  const canonicalUrl = publicHttps(article.canonical_url) ?? url;
295
301
  const image = publicHttps(article.og_image_url) ?? publicHttps(article.cover_image_url);
296
302
  return {
297
- title: article.seo_title ?? article.title,
303
+ title: article.title,
298
304
  description: article.seo_description ?? article.excerpt ?? publication.description,
299
305
  canonical: canonicalUrl,
300
306
  indexable: indexable && !article.no_index,
301
307
  openGraph: {
302
308
  type: "article",
309
+ siteName: publication.name,
303
310
  title: article.title,
304
311
  description: article.excerpt ?? void 0,
305
312
  url,
306
313
  publishedTime: article.published_at,
307
- modifiedTime: article.updated_at,
308
314
  images: image ? [image] : []
309
315
  }
310
316
  };
@@ -439,5 +445,55 @@ function referralCode(publication, url) {
439
445
  return null;
440
446
  }
441
447
  }
448
+ function ArticleProvenance({ metadata }) {
449
+ const origin = metadata?.reporting?.canonical_origin || metadata?.editorial_origin;
450
+ const reporting = metadata?.reporting;
451
+ const label = origin === "COMMENTARY" ? "Commentary" : origin === "ANALYSIS" ? "Analysis" : null;
452
+ const sources = readerSources(metadata?.citations);
453
+ const related = readerSources(reporting?.related_links?.map((url) => ({ url })));
454
+ const credit = reporting?.credit?.trim();
455
+ const byline = reporting?.original_byline?.trim();
456
+ const wire = reporting?.wire_credit?.trim();
457
+ if (!label && !credit && !byline && !wire && !sources.length && !related.length) return null;
458
+ return /* @__PURE__ */ jsxs("aside", { "aria-label": "About this reporting", className: "article-provenance", children: [
459
+ label && /* @__PURE__ */ jsx("p", { className: "article-provenance__label", children: label }),
460
+ (credit || byline) && /* @__PURE__ */ jsx("p", { children: [credit, byline].filter(Boolean).join(" \xB7 ") }),
461
+ wire && /* @__PURE__ */ jsx("p", { children: wire }),
462
+ !!sources.length && /* @__PURE__ */ jsxs("details", { children: [
463
+ /* @__PURE__ */ jsx("summary", { children: "Sources" }),
464
+ /* @__PURE__ */ jsx("ul", { children: sources.map((source) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("a", { href: source.url, rel: "noopener noreferrer", target: "_blank", children: source.title || source.publisher || new URL(source.url).hostname }) }, source.url)) })
465
+ ] }),
466
+ !!related.length && /* @__PURE__ */ jsxs("details", { children: [
467
+ /* @__PURE__ */ jsx("summary", { children: "Related reporting" }),
468
+ /* @__PURE__ */ jsx("ul", { children: related.map((link) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("a", { href: link.url, rel: "noopener noreferrer", children: [
469
+ new URL(link.url).hostname,
470
+ new URL(link.url).pathname
471
+ ] }) }, link.url)) })
472
+ ] })
473
+ ] });
474
+ }
475
+ function readerSources(input) {
476
+ if (!Array.isArray(input)) return [];
477
+ const seen = /* @__PURE__ */ new Set();
478
+ const output = [];
479
+ for (const value of input) {
480
+ if (!value || typeof value.url !== "string") continue;
481
+ try {
482
+ const url = new URL(value.url);
483
+ if (url.protocol !== "https:" || url.username || url.password || seen.has(url.href)) continue;
484
+ seen.add(url.href);
485
+ const text = (raw, limit) => {
486
+ if (typeof raw !== "string") return void 0;
487
+ const trimmed = raw.trim();
488
+ return trimmed ? Array.from(trimmed).slice(0, limit).join("") : void 0;
489
+ };
490
+ output.push({ url: url.href, title: text(value.title, 500), publisher: text(value.publisher, 255) });
491
+ if (output.length === 5) break;
492
+ } catch {
493
+ continue;
494
+ }
495
+ }
496
+ return output;
497
+ }
442
498
 
443
- export { ANALYTICS_COOKIE, ANALYTICS_VERSION, EVENTS, NEWS_CACHE, NewsError, SESSION_IDLE_MS, UTM_FIELDS, UUID, activeNearby, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, composeEdition, defaultRevalidateTargets, displayHeadline, edgeCacheControl, editorialSections, feedCacheKey, feedCacheOptions, filterNews, inventoryCacheOptions, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, publicHttps, publicUrl, publicationJsonLd, publicationMeta, referralCode, resolveCoupon, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
499
+ export { ANALYTICS_COOKIE, ANALYTICS_VERSION, ArticleProvenance, EVENTS, NEWS_CACHE, NewsError, SESSION_IDLE_MS, UTM_FIELDS, UUID, activeNearby, articleCacheKey, articleCacheOptions, articleMeta, articleUrl, attribution, briefSummary, composeEdition, defaultRevalidateTargets, displayHeadline, edgeCacheControl, editorialSections, feedCacheKey, feedCacheOptions, filterNews, inventoryCacheOptions, llmsText, matchesStoryTag, nearbyUpdates, newsArticleJsonLd, normalizedText, publicHttps, publicUrl, publicationJsonLd, publicationMeta, readerSources, referralCode, resolveCoupon, revalidateDecision, secretsMatch, sectionDefinition, sectionId, sectionPath, shortDeck, shortText, shouldServeLastKnown, storyDate, storyTowns, townSlug, validCalendarDate, walnutCreekTimes, withLastKnown };
@@ -145,6 +145,19 @@ type NewsArticle = NewsItem & {
145
145
  duration_ms?: number | null;
146
146
  };
147
147
  metadata?: {
148
+ editorial_origin?: 'UNKNOWN' | 'ORIGINAL_REPORTING' | 'DOCUMENT_REPORTING' | 'PRESS_RELEASE_BASED' | 'NEWS_PICKUP' | 'ANALYSIS' | 'COMMENTARY' | 'LICENSED_WIRE' | 'NETWORK_SYNDICATED' | 'COMMUNITY_SUBMISSION';
149
+ reporting?: {
150
+ edition_id: string;
151
+ story_id: string;
152
+ story_revision: number;
153
+ originating_publication_id: string;
154
+ original_byline?: string;
155
+ credit?: string;
156
+ related_links?: string[];
157
+ wire_credit?: string;
158
+ canonical_origin?: string;
159
+ origin?: string;
160
+ };
148
161
  cover_image?: CoverImageMetadata;
149
162
  citations?: ArticleCitation[];
150
163
  resources?: {
@@ -145,6 +145,19 @@ type NewsArticle = NewsItem & {
145
145
  duration_ms?: number | null;
146
146
  };
147
147
  metadata?: {
148
+ editorial_origin?: 'UNKNOWN' | 'ORIGINAL_REPORTING' | 'DOCUMENT_REPORTING' | 'PRESS_RELEASE_BASED' | 'NEWS_PICKUP' | 'ANALYSIS' | 'COMMENTARY' | 'LICENSED_WIRE' | 'NETWORK_SYNDICATED' | 'COMMUNITY_SUBMISSION';
149
+ reporting?: {
150
+ edition_id: string;
151
+ story_id: string;
152
+ story_revision: number;
153
+ originating_publication_id: string;
154
+ original_byline?: string;
155
+ credit?: string;
156
+ related_links?: string[];
157
+ wire_credit?: string;
158
+ canonical_origin?: string;
159
+ origin?: string;
160
+ };
148
161
  cover_image?: CoverImageMetadata;
149
162
  citations?: ArticleCitation[];
150
163
  resources?: {
@@ -1,4 +1,4 @@
1
- import { P as PublicationSnapshot, S as SnapshotHooks, a as SyncedPublication } from './snapshot-sHgQHuAF.cjs';
1
+ import { P as PublicationSnapshot, S as SnapshotHooks, a as SyncedPublication } from './snapshot-OZFP3-Up.cjs';
2
2
 
3
3
  declare function readSnapshotFile(file: string): Promise<PublicationSnapshot | null>;
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { P as PublicationSnapshot, S as SnapshotHooks, a as SyncedPublication } from './snapshot-sHgQHuAF.js';
1
+ import { P as PublicationSnapshot, S as SnapshotHooks, a as SyncedPublication } from './snapshot-OZFP3-Up.js';
2
2
 
3
3
  declare function readSnapshotFile(file: string): Promise<PublicationSnapshot | null>;
4
4
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalemule/news-publication",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "Shared server framework for ScaleMule local news publications. Walnut Creek Times is the broadsheet default; each paper supplies its own identity and feature flags.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",