Pin a feed to the top of the feed list

subscriptions.pinned, per person, set by PATCH /api/feeds/{id} {pinned} and
returned as FeedRow.pinned. Kept out of Sub, which the scanner merges into its
policy; set_subscription names its columns, so saving a feed's settings leaves
the pin alone (tested).

Pinned feeds come first in the list, a pin before the name and a rule under the
block: a pinned folder with its feeds under it, a feed from inside one lifted out
of it. The pin button is on both the feed and the folder page.

Closes #33.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-18 15:14:47 +00:00
parent fc425bffa6
commit 2d158a4540
7 changed files with 109 additions and 7 deletions

View File

@@ -1263,3 +1263,30 @@ test.describe('an item with no files, on a phone', () => {
await expect(page.locator('#detail')).not.toContainText('No files');
});
});
test('a pinned feed, even one from inside a folder, goes to the top of the list', async ({ page }) => {
const rows = page.locator('#feedlist .feed');
const group = page.locator('.feed.group').first();
if ((await group.locator('.chev').getAttribute('aria-expanded')) !== 'true') await group.locator('.chev').click();
const child = page.locator('.feed.child').first();
const name = (await child.locator('.txt b').textContent()).trim();
await child.click();
await page.locator('#content .acts [data-a="pin"]').click();
// First in the list, out of its folder, marked, and with the rule under it.
await expect(rows.first().locator('.txt b')).toHaveText(name);
await expect(rows.first()).toHaveClass(/\bpinned\b/);
await expect(rows.first()).not.toHaveClass(/\bchild\b/);
await expect(rows.first()).toHaveClass(/\blastpin\b/);
await expect(page.locator('.feed.child', { hasText: name })).toHaveCount(0);
await expect(page.locator('#content .acts [data-a="pin"]')).toHaveAttribute('aria-pressed', 'true');
// It is on the account: a reload keeps it.
await page.reload();
await expect(rows.first().locator('.txt b')).toHaveText(name);
// Unpinned, it goes back into its folder.
await rows.first().click();
await page.locator('#content .acts [data-a="pin"]').click();
await expect(page.locator('.feed.pinned')).toHaveCount(0);
await expect(page.locator('.feed.child', { hasText: name })).toHaveCount(1);
});