diff --git a/apps/readest-app/src/__tests__/utils/opds-feed.test.ts b/apps/readest-app/src/__tests__/utils/opds-feed.test.ts
new file mode 100644
index 00000000..803c9ec9
--- /dev/null
+++ b/apps/readest-app/src/__tests__/utils/opds-feed.test.ts
@@ -0,0 +1,159 @@
+import { describe, it, expect } from 'vitest';
+import { getFeed } from 'foliate-js/opds.js';
+
+const MIME_XML = 'application/xml';
+
+const parseXML = (xml: string): Document => {
+ return new DOMParser().parseFromString(xml, MIME_XML as DOMParserSupportedType);
+};
+
+describe('OPDS feed parsing', () => {
+ describe('getFeed with mixed navigation and publication entries (Copyparty)', () => {
+ // Reproduces https://github.com/readest/readest/issues/3667
+ // Copyparty mixes folder entries (navigation) and book entries (publications)
+ // in the same feed. When a folder comes first, all items were classified as
+ // navigation, causing books to show as "Untitled".
+ const copypartyRootFeed = `
+
+ Readest-Test
+
+
+ Sub/
+
+ 2026-03-28T05:37:03Z
+
+
+ Children of the Fleet - Orson Scott Card.epub
+ 2025-11-02T17:50:21Z
+
+
+
+
+`;
+
+ it('should separate navigation and publication entries in mixed feeds', () => {
+ const doc = parseXML(copypartyRootFeed);
+ const feed = getFeed(doc);
+
+ // Should have navigation items (the "Sub/" folder)
+ expect(feed.navigation).toBeDefined();
+ expect(feed.navigation!).toHaveLength(1);
+ expect(feed.navigation![0].title).toBe('Sub/');
+
+ // Should have publication items (the book)
+ expect(feed.publications).toBeDefined();
+ expect(feed.publications!).toHaveLength(1);
+ expect(feed.publications![0].metadata.title).toBe(
+ 'Children of the Fleet - Orson Scott Card.epub',
+ );
+ });
+
+ it('should handle feeds with only publications (subfolder case)', () => {
+ const subFeed = `
+
+
+
+ Children of the Fleet - Orson Scott Card.epub
+ 2025-11-02T17:50:21Z
+
+
+
+ Earth Afire - Orson Scott Card & Aaron Johnston.epub
+ 2025-11-02T17:50:22Z
+
+
+`;
+
+ const doc = parseXML(subFeed);
+ const feed = getFeed(doc);
+
+ expect(feed.navigation).toBeUndefined();
+ expect(feed.publications).toBeDefined();
+ expect(feed.publications!).toHaveLength(2);
+ expect(feed.publications![0].metadata.title).toBe(
+ 'Children of the Fleet - Orson Scott Card.epub',
+ );
+ expect(feed.publications![1].metadata.title).toBe(
+ 'Earth Afire - Orson Scott Card & Aaron Johnston.epub',
+ );
+ });
+
+ it('should handle feeds with only navigation entries', () => {
+ const navFeed = `
+
+ Root
+
+ Fiction/
+
+ 2026-03-28T05:37:03Z
+
+
+ Non-Fiction/
+
+ 2026-03-28T05:37:03Z
+
+`;
+
+ const doc = parseXML(navFeed);
+ const feed = getFeed(doc);
+
+ expect(feed.publications).toBeUndefined();
+ expect(feed.navigation).toBeDefined();
+ expect(feed.navigation!).toHaveLength(2);
+ expect(feed.navigation![0].title).toBe('Fiction/');
+ expect(feed.navigation![1].title).toBe('Non-Fiction/');
+ });
+
+ it('should handle navigation entry after publications', () => {
+ // Reverse order: publications first, then navigation
+ const reverseFeed = `
+
+
+ Some Book.epub
+ 2025-11-02T17:50:21Z
+
+
+
+ Sub/
+
+ 2026-03-28T05:37:03Z
+
+`;
+
+ const doc = parseXML(reverseFeed);
+ const feed = getFeed(doc);
+
+ expect(feed.publications).toBeDefined();
+ expect(feed.publications!).toHaveLength(1);
+ expect(feed.publications![0].metadata.title).toBe('Some Book.epub');
+
+ expect(feed.navigation).toBeDefined();
+ expect(feed.navigation!).toHaveLength(1);
+ expect(feed.navigation![0].title).toBe('Sub/');
+ });
+ });
+});
diff --git a/packages/foliate-js b/packages/foliate-js
index c5c09a97..348a64a8 160000
--- a/packages/foliate-js
+++ b/packages/foliate-js
@@ -1 +1 @@
-Subproject commit c5c09a9762505e2caf83729c988181d92be3ce70
+Subproject commit 348a64a8f16d5c06d9da9f9f63940b2d95ab0989