PageMark (working title “Online Novel Lib”) is a Kotlin Multiplatform frontend for reading and writing
web novels. Readers can browse a catalog, read chapters, and keep a library with cross-device reading
progress; authors can write and manage their own novels and chapters. It targets Android, iOS and
Web (Kotlin/Wasm) — there is no shipped Desktop app; the Web target covers the “quick look in a
UI without an emulator” need in the browser instead. A JVM target still exists in shared (it runs the
SQLite offline store tests on the host), and /desktopApp is a minimal Compose Desktop
host for local development only.
The app talks exclusively to a separate Spring Boot backend over REST (contract-first via the OpenAPI
spec in api/pageMarkBackend.yaml). It has no backend code of its own.
/androidApp — Android entry point (Application, Activity)./iosApp — iOS application entry point. Even though the UI is shared via Compose
Multiplatform, this is where the iOS app is assembled and where SwiftUI code, if any, would live./webApp — Web (Kotlin/Wasm) entry point./desktopApp — dev-only Compose Desktop host for fast UI iteration (not a release target; no CI or release build)./shared — the bulk of the app: shared logic and UI, split by source set:
commonMain — code shared across all targets. This is where most
new code should live. Packages under de.pagemark.app: ui/ (screens, view models, theme),
data/ (repositories the view models talk to), api/ (generated clients), network/ (HTTP client,
failure mapping), auth/, offline/ (cache, outbox, sync), navigation/ (routes, graphs, guards),
di/ (Koin modules).androidMain, iosMain,
wasmJsMain — platform-specific implementations only (e.g. the
concrete secure token storage backend per platform).commonTest — unit tests for the shared logic./specs — requirements and iteration planning documents (German):
anforderungen-frontend.md — full frontend requirements.iterationplan-frontend.md — iteration plan/roadmap.progress-frontend/ — one write-up per completed iteration
(what was built, decisions made, pitfalls hit).anforderungen-backend.md / iterationplan-backend.md counterparts describe the backend,
kept here only for reference.shared/commonMain)ui/ (screens, view models) → data/ (repositories) → api/ (generated clients),
offline/, auth/, network/. Lower layers never import ui/, and ui/ never imports an *Api
class or the offline cache (ArchitectureTest enforces both). Wiring lives in di/, routing in navigation/.XViewModel.kt holds the view model and its XUiState, XScreen.kt holds the
composables. Pure logic a screen needs (labels, fractions, ranges) goes in its own file next to them
(e.g. library/LibraryEntries.kt), not in the screen.<Feature><Part>.kt files of the same package (ManageNovelChapters.kt, ReaderChrome.kt). Top-level
helpers are private until another file needs them, then internal.navigation/Routes.kt; what the app chrome needs to know about a
route belongs in RouteInfo, not in startsWith checks.The app needs the PageMark backend running locally to do anything beyond compiling. It lives in a
separate sibling repository (PageMark-Backend), started with ./gradlew bootRun there (plus its
Postgres container via docker-compose). Once running:
http://localhost:8080/actuator/healthhttp://localhost:8080/api/v1 (Web/iOS Simulator) — use http://10.0.2.2:8080/api/v1
from the Android emulator instead, since localhost there refers to the emulator itself.specs/anforderungen-frontend.md, Abschnitt 10).Use the run configurations provided by the run widget in your IDE’s toolbar, or these Gradle commands:
./gradlew :androidApp:assembleDebug./gradlew :webApp:wasmJsBrowserDevelopmentRun (opens a local dev server, e.g.
http://localhost:8081)/iosApp in Xcode and run it from there (requires a Mac).Use the run button in your IDE’s editor gutter, or these Gradle tasks:
./gradlew :shared:testAndroidHostTest./gradlew :shared:jvmTest./gradlew :shared:wasmJsTest./gradlew :shared:iosSimulatorArm64Test (requires a Mac)The Ktor client under shared is generated from api/pageMarkBackend.yaml
via the openapi-generator Gradle plugin (kotlin, library=multiplatform, without an explicit
serializationLibrary — setting one causes duplicate @Serializable annotations and a build failure).
Update that file from the backend repository and rebuild to regenerate the client.
Built with Kotlin Multiplatform and Compose Multiplatform, including Kotlin/Wasm for the Web target.