FeedFlow is a multi-platform RSS reader built with Kotlin Multiplatform, Compose Multiplatform and SwiftUI. It targets Android, iOS, macOS, Windows, and Linux. The app uses SwiftUI for iOS-specific UI components and Compose for all other platforms. All the business logic is shared via Kotlin Multiplatform.
- core/: Core domain models and utilities shared across all platforms
- shared/: Main business logic, repositories, view models, and data layer
- sharedUI/: Compose UI components shared across Android and Desktop
- database/: SQLDelight database implementation
- i18n/: Internationalization resources
- feedSync/: Feed synchronization modules (Dropbox, FreshRSS, iCloud, etc)
- androidApp/: Android-specific app implementation
- iosApp/: iOS app with SwiftUI
- desktopApp/: Desktop app for Windows, Linux, and macOS
- website/: Hugo-based website
All Gradle commands in this section should be run with --quiet --console=plain.
./gradlew detekt allTests-> Run all checks including tests and linting for Shared code, Android and Desktop./gradlew detekt-> Run static analysis with Detekt for Shared code, Android and Desktop.scripts/ios-format.sh-> Format iOS code through swiftformat and swiftlint./gradlew test-> Run all tests for Shared code, Android and Desktop.scripts/refresh-translations.sh-> Regenerate i18n translation code after adding new translations./gradlew :androidApp:assembleGooglePlayDebug-> Build Android debug./gradlew :androidApp:compileGooglePlayDebugKotlin-> Quick compile check for Android (no APK assembly).scripts/run-android.sh-> Install and launch Android Google Play debug./gradlew desktopApp:run-> Run Desktop app./gradlew :desktopApp:compileKotlinJvm-> Quick compile check for Desktop./gradlew :desktopApp:jvmMainClasses-> Compile Desktop main classes (faster than full build).scripts/delete-desktop-debug-db.sh-> Delete local Desktop debug database and prefs./gradlew :shared:compileKotlinJvm-> Quick compile check for shared module only (fastest iteration)./gradlew :feedSync:feedbin:build-> Build a specific feedSync sub-module (pattern::feedSync:<module>:build)./gradlew :desktopApp:packageDistributionForCurrentOS-> Package desktop app distribution for the current OS
Ensure an emulator or device is connected via adb, then run:
.scripts/run-android.sh-> Install and launch the debug app on device/emulator
To build FeedFlow for iPhone 17 Pro simulator:
mcp__XcodeBuildMCP__build_sim_name_proj projectPath: "/Users/mg/Workspace/feedflow/feed-flow/iosApp/FeedFlow.xcodeproj" scheme: "FeedFlow" simulatorName: "iPhone 17 Pro"There could be different project path son your machine. Always use the first one. The alternative paths will be:
mcp__XcodeBuildMCP__build_sim_name_proj projectPath: "/Users/mg/Workspace/feedflow/feed-flow-2/iosApp/FeedFlow.xcodeproj" scheme: "FeedFlow" simulatorName: "iPhone 17 Pro"mcp__XcodeBuildMCP__build_sim_name_proj projectPath: "/Users/marco.gomiero/Workspace/tmp/feed-flow/iosApp/FeedFlow.xcodeproj" scheme: "FeedFlow" simulatorName: "iPhone 17 Pro"./gradlew :shared:jvmTest --tests "com.prof18.feedflow.shared.presentation.SomeTest"-> Run a specific test class on JVM./gradlew :shared:iosSimulatorArm64Test-> Run shared tests on iOS simulator
IMPORTANT: When editing code, you MUST:
- Build the project after making changes
- Fix any compilation errors before proceeding Be sure to build ONLY for the platform you are working on to save time.
Before handing off you must:
- Run
./gradlew detektto ensure all checks pass - don't run it if you modified only swift files - Run
.scripts/ios-format.shto format iOS code - only run if you made changes on the iOS app - Fix any issues found during the above steps
# Android: Copy dummy google-services.json
cp config/dummy-google-services.json androidApp/src/debug/google-services.json
cp config/dummy-google-services.json androidApp/src/release/google-services.json
# iOS: Copy dummy GoogleService-Info.plist
cp config/dummy-google-service.plist iosApp/GoogleService-Info-dev.plist
cp config/dummy-google-service.plist iosApp/GoogleService-Info.plist
# iOS: Create Config.xcconfig
cp iosApp/Assets/Config.xcconfig.template iosApp/Assets/Config.xcconfigWhen writing tests, follow the comprehensive testing guide at .ai/TESTING.md.
Key points:
- All tests extend
KoinTestBasefor dependency injection - Use Turbine for Flow testing
- Prefer fakes over mocking libraries
- Use data generators from
shared/src/commonTest/.../test/generators/ - For sync service tests, use the
feedSync/test-utilsmodule (provides mock HTTP engines and Koin modules for GReader/Feedbin) - Run specific test classes with
--tests "fully.qualified.ClassName"to iterate quickly
- DO NOT write comments for every function or class. Only write comments when the code is not self-explanatory.
- If you touch or create any business logic, ensure it's thoroughly tested with unit tests.
- DO NOT excessively use try/catch blocks for every function. Use them only for the top caller or the bottom callers, depending on the cases.
- ALWAYS run gradle tasks with the following flag:
--quiet --console=plain
- For desktop settings/details pages opened from the main screen, prefer a dedicated
DialogWindowinstead of in-window navigation. - Reuse
desktopApp/src/jvmMain/kotlin/com/prof18/feedflow/desktop/ui/components/DesktopDialogWindow.ktfor new desktop windows instead of duplicating window setup. - Use
desktopApp/src/jvmMain/kotlin/com/prof18/feedflow/desktop/main/DesktopDialogWindowNavigator.ktto open/close windows fromMainWindow(add destinations to the enum and keep visibility state there). - Keep the screen body content as a composable content block and avoid duplicating content calls; apply platform conditionals only to wrapper chrome (for example, toolbar/title handling).
- On macOS, keep transparent title bar handling inside the reusable desktop window wrapper; on other desktop platforms avoid adding duplicate custom title UI.
When creating commits:
- Use simple, one-liner commit messages
- DO NOT include phase numbers (e.g., "Phase 1", "Phase 2")
- DO NOT add "Generated with Claude Code" attribution
- DO NOT add "Co-Authored-By: Claude" attribution
- Example:
git commit -m "Add foundation for unified article parsing system"
- Native libraries in
desktopApp/resources-sandbox/macos-arm64/must be signed with the Mac App Store certificate when it is renewed. - Sign with:
codesign --force --timestamp --options runtime --sign "3rd Party Mac Developer Application: Marco Gomiero (Q7CUB3RNAK)" <path> - Verify with:
codesign -dvvv <path>
- ALWAYS build with xcodebuild with -quiet flag when building for iOS. If the command returns errors you may run xcodebuild again without the -quiet flag.
- Direct xcodebuild alternative:
xcodebuild -project iosApp/FeedFlow.xcodeproj -scheme FeedFlow -destination 'platform=iOS Simulator,name=iPhone 17 Pro' build -quiet - IMPORTANT: The project now supports iOS 26 SDK (June 2025) while maintaining iOS 18 as the minimum deployment target. Use #available checks when adopting iOS 26+ APIs.
- Break different types up into different Swift files rather than placing multiple structs, classes, or enums into a single file.
- Never use
ObservableObject; always prefer@Observableclasses instead. - Never use
Task.sleep(nanoseconds:); always useTask.sleep(for:)instead. - Avoid
AnyViewunless it is absolutely required. - Avoid force unwraps and force
tryunless it is unrecoverable.
- String resources are located in
i18n/src/commonMain/resources/locale/values-[language]/ - Run .scripts/refresh-translations.sh after adding a new translation, to re-generate the kotlin code
- NEVER add hardcoded strings in the code. Always use the i18n resources.
- NEVER try to translate other languages by yourself. Add only the English strings. The translations will be handled by professionals later.
.scripts/flatpak-build-setup.sh-> Prepare the project for Flatpak builds (sets release props, disables JetBrains JDK vendor, disables toolchain auto-provisioning, comments out Android-only Gradle plugins).scripts/disable-android-for-flatpak.sh-> Comments out all Android-related Gradle config across allbuild.gradle.ktsand build-logic files; excludesandroidAppfromsettings.gradle.kts- Flatpak packaging files live in
desktopApp/packaging/flatpak/(manifest, launch script, desktop entry, AppStream metadata, icon) - The
flatpak=trueproperty indesktopApp/src/jvmMain/resources/props.propertiesis used to disable platform-specific features (e.g., Google Drive sync) in Flatpak builds - HiDPI scaling for the JVM uses
sun.java2d.uiScaleJVM argument. For local testing:./gradlew desktopApp:run -PjvmArgs="-Dsun.java2d.uiScale=2.0"
- CI config:
.github/workflows/code-checks.yaml - Pipeline:
checks(macOS-15, detekt + allTests + swiftlint) ->build-android-app+build-desktop-app+build-ios-app(in parallel) - iOS CI build forces arm64 simulator architecture:
xcodebuild -configuration Debug -scheme FeedFlow -sdk iphonesimulator -destination "generic/platform=iOS Simulator" ARCHS=arm64 ONLY_ACTIVE_ARCH=YES build | xcbeautify --renderer github-actions - CI runs
.scripts/refresh-translations.shbefore checks; do this locally before pushing if translations changed - Debugging CI failures:
gh run list --limit=10, thengh run view <run-id> --log - Red CI recovery loop:
gh run rerun <run-id>(orgh run rerun <run-id> --failed), then fix and push until green