Skip to content

Commit c046651

Browse files
committed
feat: post preview cards + fix source color attribution
- Live AP + Bluesky preview cards on bot profile page using real diff data - Preview updates as user types prefix/suffix - Bluesky preview shows truncation at 300 chars - Fix: article upsert no longer overwrites feedId — articles keep their original source feed, so updated.xml processing doesn't steal attribution
1 parent 9fe5f33 commit c046651

4 files changed

Lines changed: 67 additions & 3 deletions

File tree

src/lib/server/workers/feed-poller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ async function processArticle(articleUrl: string, feedId: number) {
6161
.values({ feedId, url: finalUrl })
6262
.onConflictDoUpdate({
6363
target: articles.url,
64-
set: { feedId }
64+
set: { lastCheckedAt: new Date() }
6565
})
6666
.returning();
6767

src/routes/api/websub/[feedId]/+server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ async function processArticlePush(articleUrl: string, feedId: number) {
125125
const [article] = await db
126126
.insert(articles)
127127
.values({ feedId, url: finalUrl })
128-
.onConflictDoUpdate({ target: articles.url, set: { feedId } })
128+
.onConflictDoUpdate({ target: articles.url, set: { lastCheckedAt: new Date() } })
129129
.returning();
130130

131131
const [latestVersion] = await db

src/routes/bot/profile/+page.server.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,25 @@ async function processUpload(file: File, outputName: string): Promise<string | {
3434

3535
export const load: PageServerLoad = async () => {
3636
const profile = await loadBotProfile();
37-
return { profile };
37+
38+
// Load a recent non-boring diff for the post preview
39+
const recentDiff = await db.query.diffs.findFirst({
40+
where: eq(diffs.isBoring, false),
41+
with: { article: { with: { feed: true } }, newVersion: true },
42+
orderBy: (d, { desc }) => [desc(d.id)]
43+
});
44+
45+
const previewData = recentDiff ? {
46+
title: recentDiff.newVersion.title || 'Untitled Article',
47+
feedName: recentDiff.article.feed.name,
48+
titleChanged: recentDiff.titleChanged,
49+
contentChanged: recentDiff.contentChanged,
50+
charsAdded: recentDiff.charsAdded,
51+
charsRemoved: recentDiff.charsRemoved,
52+
diffId: recentDiff.id
53+
} : null;
54+
55+
return { profile, previewData };
3856
};
3957

4058
export const actions = {

src/routes/bot/profile/+page.svelte

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,21 @@
1010
let suffixInput = $state(data.profile.postSuffix || '');
1111
let bskyBudget = $derived(BSKY_LIMIT - BSKY_OVERHEAD - prefixInput.length - suffixInput.length);
1212
13+
// Live preview using a real diff
14+
const preview = data.previewData;
15+
let previewText = $derived(() => {
16+
if (!preview) return '';
17+
const changes = [
18+
preview.titleChanged ? 'Headline changed' : '',
19+
preview.contentChanged ? 'Content changed' : ''
20+
].filter(Boolean).join(' & ') || 'Article updated';
21+
const stats = `+${preview.charsAdded} / -${preview.charsRemoved} chars`;
22+
const prefix = prefixInput ? `${prefixInput} ` : '';
23+
const suffix = suffixInput ? `\n\n${suffixInput}` : '';
24+
return `${prefix}${changes} in "${preview.title}" (${preview.feedName})\n${stats}\n\nhttps://diff.example.com/diff/${preview.diffId}\nhttps://example.com/article${suffix}`;
25+
});
26+
let previewCharCount = $derived(previewText().length);
27+
1328
let fields = $derived((() => {
1429
const f = [...(profile.fields || [])];
1530
while (f.length < 4) f.push({ name: '', value: '' });
@@ -120,6 +135,27 @@
120135
— title may be truncated
121136
{/if}
122137
</div>
138+
139+
{#if preview}
140+
<div class="preview-cards">
141+
<div class="preview-card preview-mastodon">
142+
<div class="preview-header">
143+
<span class="preview-icon">🐘</span>
144+
<span class="preview-label">ActivityPub preview</span>
145+
</div>
146+
<div class="preview-body">{previewText()}</div>
147+
<div class="preview-meta">{previewCharCount} chars (no limit)</div>
148+
</div>
149+
<div class="preview-card preview-bluesky">
150+
<div class="preview-header">
151+
<span class="preview-icon">🦋</span>
152+
<span class="preview-label">Bluesky preview</span>
153+
</div>
154+
<div class="preview-body" class:preview-truncated={previewCharCount > 300}>{previewText().slice(0, 300)}{#if previewCharCount > 300}...{/if}</div>
155+
<div class="preview-meta" class:budget-over={previewCharCount > 300}>{Math.min(previewCharCount, 300)}/300 chars</div>
156+
</div>
157+
</div>
158+
{/if}
123159
</section>
124160

125161
<div class="actions">
@@ -190,6 +226,16 @@
190226
.budget-warn { color: #92400e; background: #fef3c7; }
191227
.budget-over { color: var(--color-del-text); background: var(--color-del-bg); }
192228
229+
.preview-cards { display: flex; gap: 1rem; margin-top: 1rem; flex-wrap: wrap; }
230+
.preview-card { flex: 1; min-width: 260px; border: 1px solid var(--color-border); border-radius: 0.5rem; overflow: hidden; background: white; }
231+
.preview-header { display: flex; align-items: center; gap: 0.4rem; padding: 0.5rem 0.75rem; background: #f8f8f8; border-bottom: 1px solid var(--color-border); font-size: 0.8rem; font-weight: 600; color: var(--color-muted); }
232+
.preview-icon { font-size: 1rem; }
233+
.preview-body { padding: 0.75rem; font-size: 0.85rem; line-height: 1.5; white-space: pre-wrap; word-break: break-word; color: var(--color-text); }
234+
.preview-truncated { color: var(--color-del-text); }
235+
.preview-meta { padding: 0.4rem 0.75rem; border-top: 1px solid var(--color-border); font-size: 0.75rem; color: var(--color-muted); background: #fafafa; }
236+
.preview-mastodon { border-left: 3px solid #6364ff; }
237+
.preview-bluesky { border-left: 3px solid #0085ff; }
238+
193239
.danger-zone { margin-top: 3rem; padding-top: 1.5rem; border-top: 2px solid var(--color-del-bg); }
194240
.danger-zone h2 { color: var(--color-del-text); border-bottom-color: var(--color-del-bg); }
195241
.danger-zone p { font-size: 0.85rem; color: var(--color-muted); margin-bottom: 1rem; }

0 commit comments

Comments
 (0)