feat: iOS Reader tab — full RSS reader with article reading pane
All checks were successful
Security Checks / secret-scanning (push) Successful in 4s
Security Checks / dockerfile-lint (push) Successful in 3s
Security Checks / dependency-audit (push) Successful in 13s

New third tab in the iOS app with:
- ReaderModels matching Reader API response shapes
- ReaderAPI with all endpoints (entries, feeds, categories, counters)
- ReaderViewModel with filters (Unread/Starred/All), pagination, feed management
- ReaderTabView with sub-tabs and feed filter chips
- EntryListView with infinite scroll, context menus, read/unread state
- ArticleView with WKWebView HTML rendering, star/read toggles, Save to Brain
- ArticleWebView (UIViewRepresentable WKWebView wrapper)
- FeedManagementSheet with add/delete/refresh feeds, categories
- Warm Atelier design consistent with existing app

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yusuf Suleman
2026-04-03 16:45:29 -05:00
parent 8892124b8e
commit 426adb3442
10 changed files with 1401 additions and 0 deletions

View File

@@ -40,6 +40,14 @@
A10031 /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10031; };
A10032 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C10001; };
A10033 /* FeedbackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10034; };
A10040 /* ReaderModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10040; };
A10041 /* ReaderAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10041; };
A10042 /* ReaderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10042; };
A10043 /* ReaderTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10043; };
A10044 /* EntryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10044; };
A10045 /* ArticleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10045; };
A10046 /* ArticleWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10046; };
A10047 /* FeedManagementSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10047; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@@ -76,6 +84,14 @@
B10031 /* Date+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = "<group>"; };
B10033 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B10034 /* FeedbackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackView.swift; sourceTree = "<group>"; };
B10040 /* ReaderModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderModels.swift; sourceTree = "<group>"; };
B10041 /* ReaderAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderAPI.swift; sourceTree = "<group>"; };
B10042 /* ReaderViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderViewModel.swift; sourceTree = "<group>"; };
B10043 /* ReaderTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderTabView.swift; sourceTree = "<group>"; };
B10044 /* EntryListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryListView.swift; sourceTree = "<group>"; };
B10045 /* ArticleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleView.swift; sourceTree = "<group>"; };
B10046 /* ArticleWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleWebView.swift; sourceTree = "<group>"; };
B10047 /* FeedManagementSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedManagementSheet.swift; sourceTree = "<group>"; };
C10001 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
D10001 /* Platform.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Platform.app; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
@@ -131,6 +147,7 @@
F10007 /* Fitness */,
F10014 /* Assistant */,
F10021 /* Feedback */,
F10030 /* Reader */,
);
path = Features;
sourceTree = "<group>";
@@ -268,6 +285,53 @@
name = Products;
sourceTree = "<group>";
};
F10030 /* Reader */ = {
isa = PBXGroup;
children = (
F10031 /* Models */,
F10032 /* API */,
F10033 /* ViewModels */,
F10034 /* Views */,
);
path = Reader;
sourceTree = "<group>";
};
F10031 /* Models */ = {
isa = PBXGroup;
children = (
B10040 /* ReaderModels.swift */,
);
path = Models;
sourceTree = "<group>";
};
F10032 /* API */ = {
isa = PBXGroup;
children = (
B10041 /* ReaderAPI.swift */,
);
path = API;
sourceTree = "<group>";
};
F10033 /* ViewModels */ = {
isa = PBXGroup;
children = (
B10042 /* ReaderViewModel.swift */,
);
path = ViewModels;
sourceTree = "<group>";
};
F10034 /* Views */ = {
isa = PBXGroup;
children = (
B10043 /* ReaderTabView.swift */,
B10044 /* EntryListView.swift */,
B10045 /* ArticleView.swift */,
B10046 /* ArticleWebView.swift */,
B10047 /* FeedManagementSheet.swift */,
);
path = Views;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -369,6 +433,14 @@
A10030 /* Color+Extensions.swift in Sources */,
A10031 /* Date+Extensions.swift in Sources */,
A10033 /* FeedbackView.swift in Sources */,
A10040 /* ReaderModels.swift in Sources */,
A10041 /* ReaderAPI.swift in Sources */,
A10042 /* ReaderViewModel.swift in Sources */,
A10043 /* ReaderTabView.swift in Sources */,
A10044 /* EntryListView.swift in Sources */,
A10045 /* ArticleView.swift in Sources */,
A10046 /* ArticleWebView.swift in Sources */,
A10047 /* FeedManagementSheet.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};