mcpbeat Sign in

Variant Layout Math Stays In The Variant Agent Skill

When two looks of one screen each need a fit-exactly-one-screen measurement — measure the fixed blocks, split the remainder into equal gaps, floor it at a minimum — keep a copy per look instead of hoisting one; covers the effect keys the block needs, the invisible spacer that must mirror the ratio actually drawn, and which spacer may animate. Use when the gap above or below a hero element keeps last track's size, when content that should end at the fold overflows or leaves a band of dead space, or before extracting "the same" layout maths from two screens into one helper.

1k tokens
context cost
the whole folder, loaded on every use
1
files
instructions only
0
copies elsewhere
how many repositories repackaged it
100
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/maxrave-dev/kotlin-footguns --skill variant-layout-math-stays-in-the-variant

The instruction itself

3 sections, as written by the author

Fit-one-screen maths belongs to each look

The block is small and looks shareable: measure the header, the hero frame and the info column, then

gap = (screenHeight − header − hero − info − minimum) / 2, floored at minimum, drawn above and

below the hero so it lands optically centred with the rest just reaching the fold.

// adapted
LaunchedEffect(headerDp, heroDp, infoDp, screenInfo, minimumDp) {
    if (headerDp > 0 && heroDp > 0 && infoDp > 0 && screenInfo.hDP > 0) {
        val result = (screenInfo.hDP - headerDp - heroDp - infoDp - minimumDp) / 2
        gapDp = if (result > minimumDp) result else minimumDp
    }
}

Traps

Do not hoist it. The two copies diverge, and one of them diverges silently. In this tree the hero

frame of one look is always square, while the other's changes shape while a video plays. Written

once, the shared version has to satisfy the union of both constraints — so the simple look pays for

the complex one with an extra effect run and relayout on every track change, and neither look's

author can tell which constraint belongs to whom. Two twenty-line copies with a comment each are the

cheaper artefact; see screen-shell-content-split for what genuinely does belong in the shell.

Key the effect on everything the body reads — including the measured hero height. The copy whose

hero is a fixed square reads heroDp in the body and does not list it as a key. That is a latent

stale read, invisible only because a constant-height hero never changes after the first measurement.

Copied into the look whose hero *does* change height, it became a real bug: the gap kept the previous

track's numbers and the whole column drifted. Add the key in both, so the next copy starts correct.

The measuring element is an invisible spacer, and its ratio must mirror what is actually drawn.

The hero is painted by a pager stacked behind the column, so the column reserves the space with a

spacer that reports its own height back. If the drawn frame is 16:9 while a video plays and the

spacer stays square, every number below it is wrong by the difference:

// adapted
Spacer(
    Modifier
        .fillMaxWidth()
        .padding(horizontal = 20.dp)
        .onGloballyPositioned { heroDp = with(density) { it.size.height.toDp().value.toInt() } }
        .aspectRatio(if (isVideo && watchVideo) 16f / 9 else 1f),
)

The condition on that last line has to be the *same expression* the drawn frame uses. Two independent

copies of one condition is the actual risk here — put it on the state holder if it starts drifting.

The gap spacers may animate; the measuring spacer may not. animateContentSize() on a gap makes

the layout settle smoothly when a track changes shape. On the *measured* element it feeds a stream of

intermediate heights into the effect that computes the gap, which changes the gap, which relayouts —

a measure/animate loop with no fixed point. In both copies here the two gap spacers animate and the

measuring spacer does not.

A spacer that reserves space must not take pointer input, or it eats the gestures aimed at the

element it is standing in for. A bare Spacer is inert; a Box with a background or a click is not.

The floor is a floor, not a guarantee. On a short window the arithmetic goes negative and the

branch returns minimum, so the column overflows and the page scrolls — which is right. What must

never happen is a negative gap being used: it does not clamp, it overlaps.

Measured heights held in saveable state come back before the next measurement. After a rotation

or a process restore the first frame computes the gap from the *previous* configuration's numbers and

corrects on the following pass. That is usually invisible and occasionally a one-frame jump; if it

matters, guard the whole block on screenInfo matching what the numbers were measured under.

Verifying it

  • The copies exist, and each one keys on the values it reads. Read the two blocks side by side —

they should differ only where the two looks genuinely differ:

   grep -rn --include='*.kt' --exclude-dir=build -A8 "LaunchedEffect($" . \
     | grep -B1 -A8 "HeightDp,\|heightDp," | head -60
  • Every measuring spacer, with the ratio it declares — each must match the frame its own look draws.

Match the callback receiver-agnostically: its parameter is named by whoever wrote the block and is

as often left implicit, so a pattern anchored on one spelling returns an empty listing against a

tree — or against a snippet lifted from a note like this one — that happens to use the other.

   grep -rn --include='*.kt' --exclude-dir=build -A12 "onGloballyPositioned {" . \
     | grep "aspectRatio"
  • No measuring element animates its own size. This must print nothing:
   grep -rn --include='*.kt' --exclude-dir=build -B6 "onGloballyPositioned" . \
     | grep "animateContentSize"
  • By hand, on the look whose hero changes shape: start an audio-only track, note where the info

column sits, then skip to a track that plays as video and back. The column must return to the same

place both times — a permanent offset after the round trip is the missing effect key.

How to use it

Copy the folder

Take maxrave-dev/variant-layout-math-stays-in-the-variant from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.