Reference for AI agents working on Foliary, a Kotlin Multiplatform (KMP) app using Compose Multiplatform, Orbit MVI, Koin, Room, and Kermit.
The project also contains an application website located in the website/ directory, which is an Angular application built using Tailwind CSS.
Additionally, the project contains email templates for authentication (e.g., sign-up confirmation) in the emails/ directory. This is a Maizzle project that uses Tailwind CSS to build responsive HTML emails.
| Goal | Command (run from emails/) |
|---|---|
| Develop (live reload) | npm run dev |
| Build for production | npm run build |
Key conventions:
-
Templates live in
emails/and use Maizzle components (<x-main>,<x-button>,<x-spacer>,<x-divider>). -
Front matter YAML configures
title,preheader, andbodyClass. -
Tailwind config is in
tailwind.config.jsand uses a custom Foliary color palette. -
Template variables use Go template syntax (
{{ .VariableName }}) for integration with the backend auth provider (Supabase). -
The production build outputs optimized, inlined HTML to
build_production/. -
See the Supabase email template variables documentation for available variables.
- NO NEW LIBRARIES: NEVER add any new third-party library or dependency without the user's explicit requirement or permission. If you think a library is needed, you MUST ask the user first. This is a HARD RULE.
- NO UNNECESSARY COMMENTS: Focus comments on why something is done, rather than what. Do not talk to the user via code comments.
- MIMIC EXISTING STYLE: Adhere rigorously to existing project conventions (formatting, structure, typing).
- NO GENERIC CATCH: DO NOT catch generic exceptions like
catch (throwable: Throwable)orcatch (e: Exception). This is an antipattern for coroutines as it can catchCancellationException. Catch specific exceptions or re-throwCancellationExceptionif you must catch a broad type. - NO PLAIN STRINGS IN UI: Application screens must NEVER use plain hardcoded strings. Always create a string resource entry in
@foliary/src/commonMain/composeResources/values/strings.xmland reference it viastringResource(Res.string.your_string_name).
Execute from repository root.
- FAST ITERATIONS: NEVER run
buildor all tests during development. It takes too long. Run ONLY specific tests related to the code you changed (single class or method). Running a specific test also compiles the code, catching compilation issues. All tests are executed on the CI pipeline anyway. - JVM ONLY: NEVER run Android or iOS tests unless explicitly required by the user. Always run JVM tests (
jvmTest). - NO LINTING: The
detektcommand runs automatically before every push, so there is no need for the agent to run it.
| Goal | Command |
|---|---|
| Run single test class | ./gradlew foliary:jvmTest --tests "dev.appoutlet.foliary.data.task.TaskRepositoryImplTest" |
| Run single test method | ./gradlew foliary:jvmTest --tests "dev.appoutlet.foliary.data.task.TaskRepositoryImplTest.should return tasks due today and overdue tasks" |
- Pre-push hook:
config/githooks/pre-push.shruns./gradlew detekt - PR verification:
detekt→jvmTest -Pkover koverVerify(min coverage: 80%)
| Element | Pattern | Example |
|---|---|---|
| Classes/Objects | PascalCase |
SignInViewModel |
| Functions/Properties | camelCase |
onEvent, userName |
| Constants | PascalCase |
DefaultTimeout |
| Test classes | PascalCase + Test |
TaskRepositoryImplTest |
| Test methods | Backticks | `should return tasks due today and overdue tasks` |
- Prefer
valovervar; usedata classwithvalproperties - Use
sealed interfacefor state/actions - Handle nulls with
?.let,?:, orrequireNotNull
- ViewModels:
@KoinViewModel - Services:
@Single - Repositories: Can be
@Factoryor@Singledepending on how often they are used in the app. Use@Singlefor frequently used repos, and@Factoryfor rarely used ones.
Frameworks: kotlin.test, Kotest assertions, kotlinx-coroutines-test
Important: Always use Kotest assertions (e.g., result shouldBe expected) instead of standard assert().
Skill: Before writing, updating, or reviewing tests, load the testing skill.