diff --git a/src/routes/admin.test.ts b/src/routes/admin.test.ts
index 78aa513..4af7590 100644
--- a/src/routes/admin.test.ts
+++ b/src/routes/admin.test.ts
@@ -1603,5 +1603,74 @@ describe("Admin Routes", () => {
const cfg = await repo.getConfig(feedId);
expect(cfg?.sender_in_title).toBe(false);
});
+
+ it("feed detail shows a native-feeds group when a native feed was detected", async () => {
+ const authCookie = await loginAndGetCookie();
+ const repo = FeedRepository.from(mockEnv as unknown as Env);
+ const feedId = FeedId.generate();
+ const mailboxId = MailboxId.unchecked("native.detail.07");
+ const feed = Feed.create(
+ feedId,
+ { title: "N", language: "en", allowedSenders: [], blockedSenders: [] },
+ { mailboxId },
+ );
+ feed.ingest(
+ { key: "k1", subject: "s", receivedAt: 1, size: 10 },
+ {
+ maxBytes: 1e9,
+ nativeFeeds: {
+ senderKey: "a@x.com",
+ feeds: [{ url: "https://blog.example.com/feed.xml", type: "rss" }],
+ },
+ },
+ );
+ await repo.save(feed);
+
+ const res = await request(`/admin/feeds/${feedId.value}/emails`, {
+ headers: { Cookie: authCookie },
+ });
+ const body = await res.text();
+ expect(body).toContain("native-feeds");
+ expect(body).toContain("https://blog.example.com/feed.xml");
+ });
+
+ it("native-feed dismiss route clears the flag", async () => {
+ const authCookie = await loginAndGetCookie();
+ const repo = FeedRepository.from(mockEnv as unknown as Env);
+ const feedId = FeedId.generate();
+ const mailboxId = MailboxId.unchecked("native.dismiss.09");
+ const feed = Feed.create(
+ feedId,
+ { title: "N", language: "en", allowedSenders: [], blockedSenders: [] },
+ { mailboxId },
+ );
+ feed.ingest(
+ { key: "k1", subject: "s", receivedAt: 1, size: 10 },
+ {
+ maxBytes: 1e9,
+ nativeFeeds: {
+ senderKey: "a@x.com",
+ feeds: [{ url: "https://x.com/rss", type: "rss" }],
+ },
+ },
+ );
+ await repo.save(feed);
+
+ const res = await request(
+ `/admin/feeds/${feedId.value}/native-feed/dismiss`,
+ {
+ method: "POST",
+ headers: {
+ Cookie: authCookie,
+ "Content-Type": "application/json",
+ Origin: `https://${mockEnv.DOMAIN}`,
+ },
+ },
+ );
+ expect(res.status).toBe(200);
+ const reloaded = await repo.load(feedId);
+ expect(reloaded!.hasNativeFeed()).toBe(false);
+ expect(reloaded!.nativeFeeds()).toHaveLength(1); // URLs preserved
+ });
});
});
diff --git a/src/routes/admin/emails.tsx b/src/routes/admin/emails.tsx
index 613cc3a..40c7897 100644
--- a/src/routes/admin/emails.tsx
+++ b/src/routes/admin/emails.tsx
@@ -8,7 +8,9 @@ import {
CheckIcon,
FeedFormats,
ExpiryBadge,
+ NativeFeeds,
} from "./ui";
+import { unionNativeFeeds } from "../../domain/native-feed";
import {
deleteAttachmentsForEmails,
deleteKeysWithConcurrency,
@@ -140,6 +142,7 @@ emailsRouter.get("/feeds/:feedId/emails", async (c) => {
return c.text("Feed not found", 404);
}
+ const nativeFeeds = unionNativeFeeds(feedMetadata.nativeFeeds);
const emailAddress = feedEmailAddress(feedConfig.mailbox_id, env);
return c.html(
@@ -164,6 +167,7 @@ emailsRouter.get("/feeds/:feedId/emails", async (c) => {
)}