Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/stale-coins-smash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
37 changes: 36 additions & 1 deletion server/public/admin-digest.html
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,10 @@ <h2>Edit History</h2>
return d.innerHTML.replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}

function safeHref(url) {
return /^https?:\/\//i.test(url) ? esc(url) : '#';
}

// ─── Load ───────────────────────────────────────────────────
/**
* Render Slack-format links (<url|label>) as clickable HTML.
Expand Down Expand Up @@ -427,6 +431,27 @@ <h2>Edit History</h2>
<div class="wg-card">
<h3>${esc(g.name)}</h3>
<p>${esc(g.summary)}</p>
${g.meetingRecaps && g.meetingRecaps.length > 0 ? `
<div style="margin-top:8px;">
${g.meetingRecaps.map(r => `
<div style="font-size:13px;margin-bottom:4px;">
${r.meetingUrl ? `<a href="${safeHref(r.meetingUrl)}" target="_blank" style="color:#2563eb;text-decoration:none;">${esc(r.title)}</a>` : esc(r.title)}
<span style="color:#888;"> — ${esc(r.date)}</span>
${r.summary ? `<div style="color:#555;font-size:12px;margin-top:2px;">${esc(r.summary)}</div>` : ''}
</div>
`).join('')}
</div>
` : ''}
${g.activeThreads && g.activeThreads.length > 0 ? `
<div style="margin-top:8px;padding-top:6px;border-top:1px solid #eee;">
${g.activeThreads.map(t => `
<div style="font-size:13px;margin-bottom:4px;">
${t.threadUrl ? `<a href="${safeHref(t.threadUrl)}" target="_blank" style="color:#2563eb;text-decoration:none;">${esc(t.summary)}</a>` : esc(t.summary)}
<span style="color:#888;font-size:12px;"> ${t.replyCount} replies${t.participantCount ? `, ${t.participantCount} participants` : ''}</span>
</div>
`).join('')}
</div>
` : ''}
${g.nextMeeting ? `<p style="font-size:12px;color:#888;margin-top:4px;">Next: ${esc(g.nextMeeting)}</p>` : ''}
</div>
`).join('') || '<p style="color:#aaa;font-size:13px;">No group activity this week.</p>';
Expand Down Expand Up @@ -487,13 +512,16 @@ <h3>${esc(g.name)}</h3>
el.textContent = currentValue;
el.style.color = '#333';
el.contentEditable = 'true';
el.style.whiteSpace = 'pre-wrap';
el.focus();

function save() {
el.contentEditable = 'false';
el.removeEventListener('blur', save);
el.removeEventListener('keydown', onKey);
const newValue = el.textContent.trim();
el.removeEventListener('paste', onPaste);
el.style.whiteSpace = '';
const newValue = el.innerText.trim();
if (newValue !== currentValue) {
saveField(field, newValue);
} else {
Expand All @@ -506,8 +534,15 @@ <h3>${esc(g.name)}</h3>
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); save(); }
}

function onPaste(e) {
e.preventDefault();
const text = e.clipboardData.getData('text/plain') || '';
document.execCommand('insertText', false, text);
}

el.addEventListener('blur', save);
el.addEventListener('keydown', onKey);
el.addEventListener('paste', onPaste);
}

async function saveField(field, value) {
Expand Down
12 changes: 7 additions & 5 deletions server/src/addie/services/digest-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,21 +231,23 @@ async function buildInsiderSection(): Promise<DigestInsiderGroup[]> {
const groups = await getDigestEligibleGroups();
const results: DigestInsiderGroup[] = [];

const NO_ACTIVITY = /^no recent activity\.?$/i;

for (const group of groups) {
try {
const wgContent = await buildWgDigestContent(group.id);
if (!wgContent) continue;

// Only show groups with meetings, active threads, or a substantive summary
// Only show groups with actual recent activity (meetings or threads)
const hasActivity = wgContent.meetingRecaps.length > 0 || wgContent.activeThreads.length > 0;
const NO_ACTIVITY = /^no recent activity\.?$/i;
if (!hasActivity) continue;

// Use the AI-generated activity summary if available,
// fall back to meeting recap or thread text
const hasSubstantiveSummary = wgContent.summary
&& !NO_ACTIVITY.test(wgContent.summary.trim())
&& wgContent.summary.replace(/[*_~`#>\-\s]/g, '').length >= 20;
if (!hasActivity && !hasSubstantiveSummary) continue;

// Prefer the AI-generated activity summary (includes Slack context),
// fall back to meeting recap or thread text
let summary: string;
if (hasSubstantiveSummary) {
summary = truncateAtWord(wgContent.summary!, 200);
Expand Down
2 changes: 1 addition & 1 deletion server/src/addie/templates/weekly-digest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export function renderDigestEmail(

${content.editorsNote ? `
<div style="margin: 20px 0; padding: 16px 20px; background: #f0f4ff; border-left: 4px solid #2563eb; border-radius: 0 6px 6px 0;">
<p style="font-size: 15px; color: #1a1a2e; margin: 0; line-height: 1.6;">${slackLinksToHtml(content.editorsNote)}</p>
<div style="font-size: 15px; color: #1a1a2e; margin: 0; line-height: 1.6;">${slackLinksToHtml(content.editorsNote).replace(/\n/g, '<br>')}</div>
</div>
` : ''}

Expand Down
Loading