Skip to content

Frontend

This document covers the frontend web distribution model, Compose dependency versioning rules, and frontend-app repository and ViewModel conventions.

For the broader system map, see architecture-overview.md.

frontend-app Web Distribution

frontend-app no longer builds or deploys its own container image. Its JS browser distribution is published as the consumable webDistElements directory artifact. The backing distribution task is selected by the resolved build type via the conventions.js.dist convention plugin: DEV uses jsBrowserDevelopmentExecutableDistribution (unminified, fast local builds), while STG/PROD use jsBrowserDistribution (production webpack with terser/DCE/scope hoisting). SCSS is compiled via sass-loader and extracted to a single entry CSS asset (mini-css-extract-plugin) unconditionally for every build type; the bundle's styles.scss is imported as a static @JsModule entry import (Main.kt) so webpack emits one stable main.css linked from index.html rather than an async CSS chunk plus a JS loader chunk.

backend-web-app resolves that artifact, stages it into build/caddy/dist, and packages it into the production Caddy image. backend-web-app:assemble pulls the frontend distribution transitively.

Frontend Compose Dependency Versioning

Frontend Compose modules currently include frontend-app and frontend-charts.

  • Prefer version-catalog aliases such as libs.compose.runtime, libs.compose.foundation, libs.compose.ui, and libs.compose.html.core in dependency blocks. Do not use the deprecated compose.* dependency accessors there; keep compose.desktop.currentOs only for the desktop target artifact.
  • Keep org.jetbrains.compose.* modules on the shared compose version in gradle/libs.versions.toml.
  • Keep org.jetbrains.compose.material3:material3 on the separate compose-material3 version because it does not track the rest of the JetBrains Compose modules.
  • Keep AndroidX Compose-adjacent families on their own version refs: org.jetbrains.androidx.lifecycle, org.jetbrains.androidx.navigation, androidx.savedstate, and androidx.activity:activity-compose.
  • Add compose.foundation explicitly when frontend code uses layout, focus, scrolling, pointer-input, or other foundation APIs; do not rely on those APIs arriving transitively.
  • Any module that uses Kotest artifacts should import platform(libs.kotest.bom) in the relevant configuration/source set instead of versioning individual Kotest modules directly.

frontend-app Repository And ViewModel Conventions

  • FeedsRepository and StrategiesRepository are internal interfaces with private in-file implementations. Construct them via companion object { operator fun invoke(...) } and keep DI wiring in AppComponent pointing at those factories.
  • Prefer mocking those repository interfaces directly in commonTest rather than introducing extra port types just for ViewModel tests.
  • Shared instrument-search UI state should stay generic over a feature-owned mode type. Keep instrumentsearch.ViewMode as the shared interface with displayValue, define per-screen enums such as FeedsViewMode and StrategiesViewMode in the owning feature package, and thread them through InstrumentSearchViewState<TViewMode>/SearchView<TViewMode> so each ViewModel keeps exhaustive mode handling without meaningless cross-screen values.
  • For ViewModel search/filter logic, keep helper functions private and verify behavior through exposed flows such as searchViewState instead of widening visibility for tests.
  • Prefer private top-level helpers over nested local functions when extracting non-trivial ViewModel logic.

frontend-app Test Conventions

frontend-app is a Kotlin Multiplatform UI module, so its verification is target-specific rather than a single JVM test task:

  • Shared ViewModel tests live in src/commonTest and use Kotest FunSpec, Mokkery mocks, Turbine flow assertions, and androidx.lifecycle.viewmodel.testing.viewModelScenario.
  • CoroutineTestExtension in src/commonTest sets Dispatchers.Main before each test and resets it afterward. Specs that use it should enable Kotest's coroutine test scope (coroutineTestScope = true) so the test body runs under virtual time.
  • viewModelScenario { ... }.use { ... } is the preferred harness for ViewModel lifecycle tests in common code.
  • Android host unit tests for frontend-app run through testDebugUnitTest; keep android.testOptions.unitTests.isReturnDefaultValues = true because viewModelScenario reaches Android Bundle APIs that otherwise throw "not mocked" errors under local unit tests.
  • Verify frontend-app changes with ./gradlew :frontend-app:testDebugUnitTest :frontend-app:desktopTest :frontend-app:jsTest when the change touches shared ViewModel tests or other code exercised by Android host tests. In CI, the root unit-test step runs ./gradlew test allTests, which covers backend JVM tests plus KMP allTests tasks such as :frontend-app:allTests.