diff --git a/layers/identity/server/utils/weekly-template.ts b/layers/identity/server/utils/weekly-template.ts
index 778d1ff7..985fdc16 100644
--- a/layers/identity/server/utils/weekly-template.ts
+++ b/layers/identity/server/utils/weekly-template.ts
@@ -437,18 +437,20 @@ function row(t: Tokens, options: {
/**
* A dense trending row.
*
- * Seven full editorial rows turned the email into a feed. The Skill name,
- * exact source, ranking reason, and evidence account stay. Descriptions,
- * quotes, and the duplicate source action remain in the plain-text version.
+ * Show a short description below the Skill name.
+ * Keep the source, ranking reason, and evidence account compact.
+ * Quotes and the duplicate source action remain in the plain-text version.
*/
function trendingRow(t: Tokens, options: {
siteUrl: string
owner: string
title: string
href: string
+ description: string | null
meta: string
evidence: WeeklyEvidence | null
}): string {
+ const description = trimDescription(options.description)
const evidence = options.evidence
? ` ยท @${esc(options.evidence.authorHandle)} ${options.evidence.platform === 'x' ? 'on X' : 'on Bluesky'}`
: ''
@@ -462,6 +464,7 @@ function trendingRow(t: Tokens, options: {
${esc(options.title)}
+ ${description ? ` ${esc(description)} ` : ''}
${esc(options.meta)}${evidence}
|
@@ -709,6 +712,7 @@ export function renderWeekly(input: WeeklyRenderInput): WeeklyRender {
owner: skill.owner,
title: skill.canonicalName,
href: sourceUrl,
+ description: skill.description,
meta: trendingMeta(skill, now),
evidence: skill.evidence,
})
diff --git a/test/unit/weekly-template.test.ts b/test/unit/weekly-template.test.ts
index 813870ad..45501e6e 100644
--- a/test/unit/weekly-template.test.ts
+++ b/test/unit/weekly-template.test.ts
@@ -217,11 +217,12 @@ describe('weekly template', () => {
expect(text).not.toContain('t.co/abc')
})
- it('keeps descriptions out of compact HTML rows but retains them in text', () => {
+ it('shortens long descriptions in HTML and text on a word boundary', () => {
const long = 'Forces the laziest solution that actually works, simplest, shortest, most minimal. Channels a senior dev who has seen everything: question whether the task needs to exist at all.'
const { html, text } = renderWeekly(input({ trending: [trending({ description: long })] }))
- expect(html).not.toContain('Forces the laziest solution')
+ expect(html).toContain('Forces the laziest solution')
+ expect(html).not.toContain('needs to exist at all')
expect(text).toContain('Forces the laziest solution')
expect(text).not.toContain('needs to exist at all')
// Cut on a word boundary: the last word before the ellipsis is a whole
@@ -230,13 +231,30 @@ describe('weekly template', () => {
expect(long).toContain(` ${tail} `)
})
- it('leaves a short description in the plain-text alternative', () => {
+ it('shows a short description in HTML and text', () => {
const { html, text } = renderWeekly(input({ trending: [trending({ description: 'Testing conventions.' })] }))
- expect(html).not.toContain('Testing conventions.')
+ expect(html).toContain('Testing conventions.')
expect(text).toContain('Testing conventions.')
})
+ it('escapes description markup in HTML and preserves it in text', () => {
+ const description = 'Use & test.'
+ const { html, text } = renderWeekly(input({ trending: [trending({ description })] }))
+
+ expect(html).toContain('Use <script>alert("skill")</script> & test.')
+ expect(html).not.toContain('