Skip to content
Merged
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
3 changes: 3 additions & 0 deletions backend/src/services/feed-proxy-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,9 @@ export interface SembleContextResult {
similar?: Array<{
url: string;
title: string | null;
/** Optional: proxy deploys separately and caches bundles ~5 min, so older
* payloads arrive without it. */
description?: string | null;
siteName: string | null;
saveCount: number;
}>;
Expand Down
10 changes: 9 additions & 1 deletion feed-proxy/src/semble-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,13 @@ describe('fetchSembleContext similar URLs', () => {
{ url: 'https://connected.example/story', metadata: { title: 'Already connected' } },
...Array.from({ length: 10 }, (_, i) => ({
url: `https://similar.example/${i}${i === 1 ? '/' : ''}`,
metadata: { title: `Similar ${i}`, siteName: 'Similar Site' },
// Every other entry omits `description` — Semble's metadata is not
// guaranteed to carry one, and the reader has to cope either way.
metadata: {
title: `Similar ${i}`,
siteName: 'Similar Site',
...(i % 2 === 0 ? { description: `About similar ${i}` } : {}),
},
urlLibraryCount: i + 1,
})),
{ url: 'https://similar.example/1', metadata: { title: 'Duplicate' } },
Expand All @@ -122,9 +128,11 @@ describe('fetchSembleContext similar URLs', () => {
expect(context?.similar[0]).toEqual({
url: 'https://similar.example/0',
title: 'Similar 0',
description: 'About similar 0',
siteName: 'Similar Site',
saveCount: 1,
});
expect(context?.similar[1]?.description).toBeNull();
expect(context?.similar.some((item) => item.url.includes('connected.example'))).toBe(false);
expect(new Set(context?.similar.map((item) => item.url)).size).toBe(8);
});
Expand Down
9 changes: 8 additions & 1 deletion feed-proxy/src/semble-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,13 @@ export interface SembleContext {
imageUrl: string | null;
};
}>;
similar: Array<{ url: string; title: string | null; siteName: string | null; saveCount: number }>;
similar: Array<{
url: string;
title: string | null;
description: string | null;
siteName: string | null;
saveCount: number;
}>;
truncated: { savers: boolean; notes: boolean; collections: boolean; connections: boolean };
incomplete: boolean;
source: 'semble-api' | 'constellation-fallback';
Expand Down Expand Up @@ -330,6 +336,7 @@ export async function fetchSembleContext(rawUrl: string): Promise<SembleContext
{
url: candidateUrl,
title: text(entry.metadata?.title, 500),
description: text(entry.metadata?.description),
siteName: text(entry.metadata?.siteName, 256),
saveCount: count(entry.urlLibraryCount),
},
Expand Down
21 changes: 21 additions & 0 deletions frontend/src/lib/components/feed/AtmospherePanel.component.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,12 +308,23 @@ describe('AtmospherePanel Semble similar articles', () => {
document.body.innerHTML = '';
});

// Entry 0 carries a description, entry 1 has an explicit null, and 2 onwards
// omit the field entirely — the shape older proxy builds and bundles cached
// before the field existed still send.
const description = (i: number) =>
i === 0
? { description: 'A calm account of the same idea' }
: i === 1
? { description: null }
: {};

const similar = (n: number) =>
Array.from({ length: n }, (_, i) => ({
url: `https://similar${i}.example/story`,
title: i === 1 ? null : `Similar article ${i}`,
siteName: i === 1 ? null : 'Example Review',
saveCount: i + 1,
...description(i),
}));

it('renders links, metadata, hostname fallback, and a Semble foot link', () => {
Expand All @@ -326,6 +337,16 @@ describe('AtmospherePanel Semble similar articles', () => {
expect(target.querySelector('.semble-foot')?.textContent).toContain('See all on Semble');
});

it('carries the description under the title, and nothing at all without one', () => {
const target = render({ sembleContext: { ...emptyContext, similar: similar(3) } }, 'related');
const items = target.querySelectorAll('.similar-list li');
expect(items[0].querySelector('.similar-desc')?.textContent).toBe(
'A calm account of the same idea'
);
expect(items[1].querySelector('.similar-desc')).toBeNull();
expect(items[2].querySelector('.similar-desc')).toBeNull();
});

it('uses the connection save affordance and saved state', () => {
const saved: string[] = [];
const target = render(
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/lib/components/feed/AtmospherePanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -1136,6 +1136,9 @@
rel="noopener"
onclick={(e) => e.stopPropagation()}>{similarLabel(item)}</a
>
{#if item.description}
<p class="similar-desc">{item.description}</p>
{/if}
{#if (item.title && item.siteName) || item.saveCount > 1}
<span class="similar-meta">
{#if item.title && item.siteName}{item.siteName}{/if}
Expand Down Expand Up @@ -1528,6 +1531,20 @@
min-width: 0;
}

/* Same register as .connection-note: muted prose under a link, clamped hard so
four of these stay a list you can scan rather than a wall of blurbs. */
.similar-desc {
margin: 0.125rem 0 0;
font-size: var(--text-sm);
line-height: var(--leading-snug);
color: var(--color-text-secondary);
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}

.similar-meta {
display: block;
margin-top: 0.0625rem;
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/lib/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,9 @@ export interface SembleContext {
similar?: Array<{
url: string;
title: string | null;
/** Optional: the proxy deploys separately and caches bundles ~5 min, so
* payloads predating the field still arrive. */
description?: string | null;
siteName: string | null;
saveCount: number;
}>;
Expand Down
Loading