The checks that run without you asking are the ones that scale
Compose ships lint rules for the modifier convention, remember misuse, unstable collections and more. Adding your own is how a design-system convention becomes a build failure instead of a code-review comment.

Day 94 of 100, closing the tools pillar. Ninety-three days of conventions, and one mechanism that makes any of them stick.
The symptom
A convention that exists only in review comments:
"Can you take a
modifierparameter here?" "This should be the first optional parameter." "Thatrememberis missing a key." "Don't pass theNavControllerdown."
The same four comments, on every pull request, from whichever reviewer noticed. New joiners learn them slowly and inconsistently. The reviewer who cares most becomes a bottleneck, and when they're on holiday the conventions lapse.
Why the obvious approach fails
The obvious approach is documentation — a style guide, a wiki page, an onboarding doc.
Documentation is a reminder for someone who already remembers to look. It doesn't run, so it can't fail a build, and its adherence decays quietly. Six months on, half the codebase follows it and nobody can say which half.
The other obvious approach — more careful review — makes a person the enforcement mechanism, which doesn't scale and doesn't survive turnover.

The actual mechanism
Compose ships lint checks that encode its own conventions. They run in the IDE as you type
and in ./gradlew lint on CI:
ComposableNaming — @Composable functions that return Unit should be
PascalCase; ones that return a value should be camelCase. That's the
OrderCard / rememberOrderState distinction from Day 17, enforced.
ModifierParameter — a composable emitting UI should take a modifier: Modifier = Modifier as its first optional parameter. Day 19's convention, checked.
UnrememberedMutableState — mutableStateOf not wrapped in remember, which resets
every recomposition. One of the highest-value checks in the set, because the symptom
(state that never changes) doesn't look like a missing remember.
FrequentlyChangedStateReadInComposition — reading something like a scroll offset
during composition. Day 82's problem, caught at edit time.
AutoboxingStateCreation — mutableStateOf(0) where mutableIntStateOf(0) avoids
boxing. Day 10's note, automated.
CoroutineCreationDuringComposition — launching from a composable body rather than an
effect or a callback. Day 9's rule.
ProduceStateDoesNotAssignValue, RememberReturnType,
UnrememberedAnimatable — smaller, and each catches a specific real bug.
The list is worth reading once in full. Several encode things this series spent a post explaining, and having them as warnings means nobody has to remember the post. That is the honest summary of the tools pillar's value: a check is a piece of knowledge that doesn't need to be transmitted.
Turning warnings into failures
The default severity for most of these is a warning, which in a large codebase means invisible. Promoting the ones you care about is the step that changes behaviour:
android {
lint {
warningsAsErrors = false
error += listOf(
"UnrememberedMutableState",
"CoroutineCreationDuringComposition",
"ModifierParameter",
)
abortOnError = true
}
}
Do this incrementally. Promoting everything at once on an existing codebase produces four
hundred failures and a baseline.xml that suppresses them all — which is the same as not
having done it.
The workable path: promote one rule, fix its existing violations in a dedicated change, then promote the next. Each one becomes permanently true rather than aspirationally true.
Slack's compose-lints
Worth knowing about, because it covers conventions the official set doesn't:
lintChecks("com.slack.lint.compose:compose-lint-checks:<version>")
It adds checks for things this series argued for: composables that shouldn't return values,
mutable parameters, state hoisting violations, ViewModel instances forwarded into child
composables, missing content descriptions, and the "don't pass a NavController down" rule
from Day 91.
It's the closest thing to an off-the-shelf encoding of Compose community consensus, and adopting it is cheaper than writing the equivalent rules yourself.
Writing your own
For a convention specific to your codebase — "screens must use AppTheme", "no direct
Color(0x…) outside the theme package", "our AppButton rather than Material's Button"
— a custom rule is a day's work and permanent.
The shape, roughly:
class NoRawColorDetector : Detector(), SourceCodeScanner {
override fun getApplicableMethodNames() = listOf("Color")
override fun visitMethodCall(context: JavaContext, node: UCallExpression, method: PsiMethod) {
if (context.file.path.contains("/ui/theme/")) return
context.report(
ISSUE, node, context.getLocation(node),
"Use a MaterialTheme colour role rather than a literal (Day 44)",
)
}
}
The judgment about which conventions deserve this: rules that are objective, mechanically checkable, and that a reviewer currently repeats. A rule requiring taste — "this component is doing too much" — isn't a lint rule, and trying to make it one produces false positives that teach people to suppress warnings.
The editor actions
The other half of the tooling, and less discussed:
Live templates — comp scaffolds a composable, prev a preview. Small, and they push
people toward the conventional shape by default.
"Surround with" wrapping a selection in a Box, Row, Column or if, which is the
most-used refactoring in Compose work.
"Extract composable" pulls a selection into a new function with its parameters inferred — the mechanical half of Day 7's "move the read down".
The gutter preview icon on any @Preview, and the "copy image" action on a rendered
preview, which is the fastest way to get a screenshot into a pull request.
None is remarkable individually. Together they make the conventional path the low-effort one, which is the same argument as lint from the other direction. Lint raises the cost of the wrong shape; the editor actions lower the cost of the right one, and a team feels the second more than the first.
How to prove it
Run ./gradlew lint on an existing codebase and read the Compose-category findings. Most
teams find UnrememberedMutableState or AutoboxingStateCreation hits they didn't know
about — real bugs and real allocations that nobody's review caught.
Then pick the convention your team repeats most in review, check whether a rule exists for
it, and promote it to error. The measurement is how many times that comment appears in
the next month's pull requests.
The honest version of that measurement includes the reverse: count how often the rule fires on code that was actually fine. More than occasionally means the rule encodes taste rather than a fact, and a rule people routinely suppress is worse than none — it teaches the whole team that warnings are noise.
What this generalizes to
The tools pillar's conclusion: a convention that isn't enforced is a preference. A document describes what should happen; a lint rule decides whether the build passes. Only one of those survives turnover, deadline pressure and a reviewer on holiday.
Three days of tooling share a shape. A preview applies pressure toward previewable design (Day 92). The inspector makes invisible recomposition visible (Day 93). Lint makes a convention mechanical. In each case the tool's value isn't what it shows you once — it's the behaviour it makes cheap enough to sustain.
Which is the argument for spending a day on tooling setup at the start of a project rather than at the point of pain. A lint rule added on day one prevents a class of mistake for the project's whole life; the same rule added in year two comes with four hundred violations and a negotiation.
Tomorrow, Day 95 opens the final pillar: edge-to-edge and the system surfaces.
Day 94 of a 100-day series on Jetpack Compose, working through the official documentation in order. Source: Compose lint checks.