android: teach the linters this codebase's conventions, and fix two real nits
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 2m33s
Android / Kotlin + Rust (debug APK) (push) Failing after 4m49s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 4m59s
Desktop (Tauri) / Update manifest (push) Successful in 5s

Fourth run got the whole native pipeline through — cargo-ndk built all four
ABIs and uniffi generated the Kotlin — and then failed on style.

Two genuine mistakes, fixed:
  * BoardViewModel's constructor parameter needed its own line.
  * PaddingValues was written fully-qualified inline, which ktlint read as a
    method chain. Importing it is what the rule was actually asking for, and
    what the line should have said anyway.

The other ten were the tools not knowing this codebase:
  * @Composable functions are PascalCase by universal Compose convention.
    Exempted in BOTH .editorconfig (ktlint) and config/detekt.yml — they have to
    agree or one of them is always wrong.
  * MagicNumber on `private val Brand = Color(0xFFF5C518)`. The rule asks for a
    well-named constant; that line IS one. ignorePropertyDeclaration.
  * TooGenericExceptionCaught in the ViewModel and Application. Deliberate and
    already commented: a note that fails to save must become a visible error
    banner rather than a crash, and the store failing to open must still let the
    app start so it can explain itself. Scoped to those two paths, not disabled
    globally — everywhere else the rule is right.

Verified locally this time, both linters clean, using the SAME pinned CLIs from
ci-android:36 that the lane runs. ktlint and detekt are a formatter and a static
analyzer — the same category as cargo fmt and clippy, which is the precedent
ci-requirements already sets. No build was run locally.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-18 16:04:40 -04:00
co-authored by Claude Opus 5
parent 5d0de7a682
commit dee71dffb3
4 changed files with 49 additions and 8 deletions
+15
View File
@@ -0,0 +1,15 @@
root = true
[*.{kt,kts}]
# ktlint's standard function-naming rule doesn't know about Compose, where
# PascalCase @Composable functions are the universal convention — every
# mainstream Compose codebase would fail it. This is ktlint's own supported
# exemption, and it mirrors the equivalent detekt override in config/detekt.yml.
ktlint_function_naming_ignore_when_annotated_with = Composable
# 120 rather than ktlint's looser default: this is a phone UI with deeply nested
# Compose calls, and a hard-ish ceiling is what keeps the nesting from becoming
# unreadable rather than merely long.
max_line_length = 120
indent_size = 4
insert_final_newline = true
@@ -2,6 +2,7 @@ package com.fabledsword.thoughtsync.ui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@@ -106,7 +107,7 @@ private fun CaptureField(
private fun NoteList(notes: List<Note>) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = androidx.compose.foundation.layout.PaddingValues(16.dp),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
// Keyed by id so Compose reuses rows across a refresh instead of
@@ -31,7 +31,9 @@ data class BoardState(
* (The sync methods are the exception: those are `suspend` on the Kotlin side
* already, because uniffi bridges the Rust async to coroutines.)
*/
class BoardViewModel(private val core: ThoughtSync) : ViewModel() {
class BoardViewModel(
private val core: ThoughtSync,
) : ViewModel() {
var state by mutableStateOf(BoardState())
private set
+29 -6
View File
@@ -1,19 +1,42 @@
# Per-rule overrides layered on top of detekt's defaults
# (`buildUponDefaultConfig = true` in the :app `detekt {}` block).
# (`--build-upon-default-config` on the CLI invocation in the Android lane).
#
# The pre-2.0 `build:` top-level was removed; failure-on-finding is controlled by
# the Gradle DSL's `failOnSeverity` option instead.
# The pre-2.0 `build:` top-level was removed; failure is controlled by the CLI's
# exit code instead.
naming:
# Composables conventionally use PascalCase function names. Matches every
# mainstream Compose codebase, and Minstrel's config for the same reason.
# mainstream Compose codebase, and mirrors the ktlint exemption in
# android/.editorconfig — the two tools have to agree or one of them is always
# wrong.
FunctionNaming:
ignoreAnnotated:
- "Composable"
style:
# The generated uniffi bindings are excluded at the Gradle level, but a
# magic-number complaint about a Compose dp value is noise rather than a smell.
MagicNumber:
ignoreAnnotated:
- "Composable"
# Colour literals and dp constants are declared as named properties, which is
# exactly the "define it as a well-named constant" the rule asks for — the
# number simply appears in the declaration itself. Flagging
# `private val Brand = Color(0xFFF5C518)` would demand a constant holding the
# constant.
ignorePropertyDeclaration: true
exceptions:
TooGenericExceptionCaught:
# Catching broadly is DELIBERATE in these two places, and each site says so.
#
# * the ViewModel — a note that fails to save must become a visible error
# banner, never a crash. Narrowing this would mean an unanticipated
# failure takes the app down instead of being reported, which is strictly
# worse for the user.
# * the Application — the store failing to open is the one thing that must
# still let the app start, so it can explain itself.
#
# Scoped to those paths rather than disabled globally: elsewhere the rule is
# right and still applies.
excludes:
- "**/ui/**"
- "**/ThoughtSyncApplication.kt"