@sarimarcus/content-sites-ui 0.36.0 → 0.38.0

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/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  All packages in the platform release in lockstep; entries are per release version.
4
4
 
5
+ ## 0.38.0 (2026-10-15)
6
+
7
+ - `editorial`: new `ArticleHubSection`, a titled hub section (heading over a slotted card grid) that renders nothing when `count` is 0; `ArticleMetadata` now takes its "Updated" test from core `isUpdatedAfterPublication` (SLA-3120).
8
+
9
+ ## 0.37.0 (2026-10-14)
10
+
11
+ - No changes; lockstep release with core (SLA-3083).
12
+
5
13
  ## 0.36.0 (2026-10-14)
6
14
 
7
15
  - New `./webmcp` export (`setupWebMcp`, also re-exported from `./layout`): registers read-only WebMCP tools on `document.modelContext` when the browser exposes it, else does nothing. `search: true` adds `search_site` over the site's Pagefind index; `remoteEndpoint` + a lazy `remoteTools` import mirror a remote MCP server's tools through stateless JSON-RPC `tools/call`. No markup change; nothing loads until an agent calls a tool (SLA-3084).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarimarcus/content-sites-ui",
3
- "version": "0.36.0",
3
+ "version": "0.38.0",
4
4
  "description": "Universal Astro UI components, layout shell and design tokens shared by the content sites.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -50,6 +50,6 @@
50
50
  "@lucide/astro": "^1.23.0",
51
51
  "astro": "^7.0.6",
52
52
  "tailwindcss": "^4.1.18",
53
- "@sarimarcus/content-sites-core": "0.36.0"
53
+ "@sarimarcus/content-sites-core": "0.38.0"
54
54
  }
55
55
  }
@@ -0,0 +1,21 @@
1
+ ---
2
+ /**
3
+ * ArticleHubSection — one titled section of the articles hub (trending, latest, recently updated): a heading over
4
+ * the slotted card grid. `count` is an optional render guard: 0 renders nothing, so no empty heading is left behind.
5
+ */
6
+ interface Props {
7
+ heading: string;
8
+ id: string;
9
+ count?: number;
10
+ class?: string;
11
+ }
12
+
13
+ const { heading, id, count, class: className = 'mb-14' } = Astro.props;
14
+ ---
15
+
16
+ {(count === undefined || count > 0) && (
17
+ <section class={className} aria-labelledby={id}>
18
+ <h2 id={id} class="text-2xl sm:text-3xl font-normal font-display text-foreground mb-6">{heading}</h2>
19
+ <slot />
20
+ </section>
21
+ )}
@@ -12,6 +12,7 @@ export { default as ArticleKeyFacts } from './ArticleKeyFacts.astro';
12
12
  export { default as AuthorCard } from './AuthorCard.astro';
13
13
  export { default as ArticleCard } from './ArticleCard.astro';
14
14
  export { default as ArticleGrid } from './ArticleGrid.astro';
15
+ export { default as ArticleHubSection } from './ArticleHubSection.astro';
15
16
  export { default as ArticleCategoryNav } from './ArticleCategoryNav.astro';
16
17
  export { default as ArticleTopicPage } from './ArticleTopicPage.astro';
17
18
  export { default as ArticleTopicPager } from './ArticleTopicPager.astro';
@@ -18,6 +18,8 @@ import { SILKTIDE_STYLESHEET } from './resources.ts';
18
18
 
19
19
  interface Props {
20
20
  title: string;
21
+ /** Optional social/Discover headline for `og:title` and `twitter:title` when `<title>` uses a shorter SEO variant. */
22
+ ogTitle?: string;
21
23
  description: string;
22
24
  siteName: string;
23
25
  /** Absolute origin, e.g. `https://www.example.com`. */
@@ -53,6 +55,7 @@ interface Props {
53
55
 
54
56
  const {
55
57
  title,
58
+ ogTitle,
56
59
  description,
57
60
  siteName,
58
61
  siteUrl,
@@ -82,11 +85,12 @@ const {
82
85
  footerClass,
83
86
  } = Astro.props;
84
87
 
88
+ const socialTitle = ogTitle ?? title;
85
89
  const baseUrl = import.meta.env.BASE_URL.replace(/\/$/, '');
86
90
  const canonicalUrl = providedCanonicalUrl
87
91
  ? normalizeSiteUrl(providedCanonicalUrl, siteUrl, baseUrl)
88
92
  : toAbsoluteSiteUrl(normalizePagePath(Astro.url.pathname, baseUrl), siteUrl, baseUrl);
89
- const imageAlt = ogImageAlt ?? `${title} - ${siteName}`;
93
+ const imageAlt = ogImageAlt ?? `${socialTitle} - ${siteName}`;
90
94
 
91
95
  function normalizeArticleTime(dateStr: string | undefined): string | undefined {
92
96
  if (!dateStr) return undefined;
@@ -123,7 +127,7 @@ const isArticleMeta = Boolean(articleAuthorUrl || publishedTime || modifiedTime
123
127
 
124
128
  <meta property="og:type" content={ogType ?? (isArticleMeta ? 'article' : 'website')} />
125
129
  <meta property="og:url" content={canonicalUrl} />
126
- <meta property="og:title" content={title} />
130
+ <meta property="og:title" content={socialTitle} />
127
131
  <meta property="og:description" content={description} />
128
132
  <OgImageMeta image={ogImage} alt={imageAlt} siteUrl={siteUrl} baseUrl={baseUrl} />
129
133
  <meta property="og:site_name" content={siteName} />
@@ -137,7 +141,7 @@ const isArticleMeta = Boolean(articleAuthorUrl || publishedTime || modifiedTime
137
141
  <meta name="twitter:card" content="summary_large_image" />
138
142
  {twitterHandle && <meta name="twitter:site" content={twitterHandle} />}
139
143
  {twitterHandle && <meta name="twitter:creator" content={twitterHandle} />}
140
- <meta name="twitter:title" content={title} />
144
+ <meta name="twitter:title" content={socialTitle} />
141
145
  <meta name="twitter:description" content={description} />
142
146
 
143
147
  <title>{title}</title>
@@ -14,6 +14,7 @@
14
14
  * changes no byte.
15
15
  */
16
16
  import { Image } from 'astro:assets';
17
+ import { isUpdatedAfterPublication } from '@sarimarcus/content-sites-core/articles';
17
18
  import type { ArticleAuthor } from './types';
18
19
  import { LABELS } from '../internal/labels';
19
20
 
@@ -76,7 +77,7 @@ function formatDate(dateStr: string | undefined): string {
76
77
 
77
78
  const publishedFormatted = formatDate(datePublished);
78
79
  const modifiedFormatted = formatDate(dateModified);
79
- const showModified = !!dateModified && (!datePublished || dateModified.slice(0, 10) !== datePublished.slice(0, 10));
80
+ const showModified = isUpdatedAfterPublication({ datePublished, dateModified });
80
81
 
81
82
  const name = author?.name;
82
83
  const portraitAlt = `${portraitLabel} ${name}`;