fix(paywalls): clamp negative margins/padding to zero#3559
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3559 +/- ##
=======================================
Coverage 80.35% 80.35%
=======================================
Files 377 377
Lines 15417 15417
Branches 2140 2140
=======================================
Hits 12389 12389
Misses 2171 2171
Partials 857 857 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
facumenzella
marked this pull request as ready for review
June 8, 2026 16:43
tonidero
approved these changes
Jun 9, 2026
tonidero
left a comment
Contributor
There was a problem hiding this comment.
A couple of suggestions, but I think this is ok!
Contributor
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 7b790375df90b639e6e74334b68e8197c68ebeb7. Configure here.
📸 Snapshot Test1 added, 591 unchanged
🛸 Powered by Emerge Tools |
Android does not support negative padding, and the SDK applies margins via Compose's Modifier.padding(...) too. When the dashboard sends negative top/bottom/leading/trailing values, they render incorrectly. Clamp negatives to zero at the single conversion choke point (Padding.toPaddingValues), so both margins and padding are protected and any downstream consumers that compute from the resulting PaddingValues get clamped values for free. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses review feedback: - Log a warning in Padding.toPaddingValues when negative values are received, to surface the dashboard misconfiguration. - Add a StackComponentView preview with negative padding/margin so the clamped rendering is visually validated (Emerge snapshots @Preview functions). PaddingTest now runs under Robolectric so android.util.Log is available. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses Cursor Bugbot: `toPaddingValues` is also called from `derivedStateOf` blocks, so logging inside it re-emitted the same warning whenever a partial override recomputed (tab/package/window-size/variable changes), even though the dashboard values had not changed. Gate the warning behind an opt-in `warnIfNegative` flag that only StyleFactory passes (once per paywall build). Clamping still happens unconditionally on every call, so the recompute path stays correct and silent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
facumenzella
force-pushed
the
facundo/clamp-negative-margins
branch
from
June 10, 2026 15:30
7b79037 to
0279786
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Description
Android does not support negative padding, and the RevenueCatUI SDK applies component margins via Compose's
Modifier.padding(...)as well. The Paywalls dashboard can send negativetop/bottom/leading/trailingvalues, which render incorrectly on Android.This clamps negative values to
0at the single conversion choke point,Padding.toPaddingValues(), so both margins and padding are protected. Because clamping happens before the value is stored in component styles/state, any downstream consumers that compute from the resultingPaddingValues(e.g.ButtonComponentView'smargin.calculateTopPadding()) get clamped values for free.When clamping occurs, a warning is logged so the dashboard misconfiguration is visible. The warning is opt-in via a
warnIfNegativeflag that onlyStyleFactorypasses (once per paywall build); the conversion itself is pure, so hot recompute paths (derivedStateOf) clamp silently and never spam the same warning on tab/package/size/variable changes.Why this layer
toPaddingValues()is the one place every margin and padding flows through (bothStyleFactorybase styles and per-component partial overrides).Paddingmodel itself: that is the serialized source of truth, and clamping there would corrupt round-trips.Scope note
The ask was about margins specifically, but this choke point covers both margins and padding. There are zero intentional negative padding/margin values anywhere in source or JSON fixtures (only
0and positives), and negative padding is unsupported on Android regardless, so clamping both is correct behavior rather than scope creep, and avoids a future footgun where a new margin call site forgets to clamp.Tests
PaddingTestnext to the conversion (the existingPaddingTestslives in:purchasesand tests the model, not the conversion). Covers positive pass-through, all-negative clamping, mixed, and clamping withwarnIfNegativeenabled. Runs under Robolectric soandroid.util.Log(used by the warning) is available.StackComponentViewpreview with negative padding and margin so the clamped rendering is visually validated via Emerge snapshots.AI session context
AI Context
Metadata
Goal
Clamp negative spacing values sent by the Paywalls dashboard to zero on Android, since Android does not support negative padding and margins are applied via
Modifier.padding(...). Focus on margins.Initial Prompt
"Apparently we don't support negative padding in android, but the dashboard can send it. Can we clamp those? Margins specifically."
Important Follow-up Prompts
Agent Contribution
Padding.toPaddingValues()and mapped all margin/padding call sites (StyleFactory once-per-build, per-componentderivedStateOfrecompute paths, view application sites).coerceAtLeast(0.0)on all four axes (unconditional).warnIfNegativeflag; only StyleFactory's 17 conversion sites pass it, so the warning fires once per paywall build and thederivedStateOfpaths stay silent.PaddingTest) under Robolectric; added aStackComponentViewnegative-padding preview.Human Decisions
Key Implementation Decisions
Padding.toPaddingValues(); gate the warning behind an opt-inwarnIfNegativeflag used only by StyleFactory.derivedStateOf.toPaddingValues()(spams on state changes, per Cursor); clamping in thePaddingmodel (corrupts serialized round-trips); clamping at the ~20.padding(...)application sites (footgun); a process-wide dedupe set (mutable global state).Files / Symbols Touched
ui/revenuecatui/.../components/ktx/Padding.ktPadding.toPaddingValues(warnIfNegative: Boolean = false)warnIfNegativeonly gates the logui/revenuecatui/.../components/style/StyleFactory.ktwarnIfNegative = trueat the 17 once-per-build conversion sitesderivedStateOf)ui/revenuecatui/.../components/ktx/PaddingTest.ktLogPaddingTestui/revenuecatui/.../components/stack/StackComponentView.ktStackComponentView_Preview_NegativePaddingAndMarginClampedDependencies / Config / Migrations
Validation
./gradlew :ui:revenuecatui:testDefaultsBc8DebugUnitTest --tests "...ktx.PaddingTest": BUILD SUCCESSFULValidation Gaps
ui/revenuecatui/src/test/snapshotsis git-ignored/absent in this checkout; snapshots run on CI / the snapshots repo.Review Focus
warnIfNegative, notderivedStateOf.Risks / Reviewer Notes
0/positive values; negative padding is unsupported on Android.Non-goals / Out of Scope
Paddingmodel or backend contract.Omitted Context