I contributed to the Lichess mobile app.

That is a fun sentence to be able to write.

The contribution was not a new variant, a dramatic board redesign, or an algorithm that finally pairs me with somebody whose rating begins with the same digit as mine. It was one blank checkbox.

The checkbox had nothing next to it. Now it does not appear when there is nothing to put next to it.

Open-source software is beautiful.

The papercut

In the Lichess mobile app’s Home widgets settings, edit mode could show a checkbox with an empty row beside it. In the screenshot attached to my pull request, it sits between Quick pairing and Blog like an option whose label has been redacted.

It was small. The app still worked. Games could be played. Nobody’s queen was in danger because of it.

But it was visibly wrong, and once I noticed it I wanted to know why it existed.

The invisible widget

The settings screen wraps each editable home widget in _EditableWidget. That wrapper always renders a checkbox next to the widget’s content. The content is also the row’s only visible label.

Usually that is fine. But some widgets can decide that they have nothing to display and return SizedBox.shrink(), which is Flutter’s tidy way of rendering no space at all.

The featured-tournaments widget could do exactly that when the server supplied tournaments but none of them were supported by the app. Its shouldShow condition only checked whether server content existed. The wrapper believed there was a row to edit, rendered the checkbox, and placed it beside content that had intentionally vanished.

The friends carousel had the same class of problem when the user followed nobody.

So the mystery checkbox was not really empty. It belonged to an invisible widget.

The fix

The fix was pleasantly narrow: make those widgets report that they should be shown only when they actually have content to render.

Loading and error states keep their existing behavior. The shimmer can still appear while data loads, and error text can still explain a failure. The change only removes edit controls for sections whose normal content would collapse to nothing.

I also added widget tests for both sides of the featured-tournaments case:

  • supported tournament content produces the editable row; and
  • unsupported tournament content does not leave a blank checkbox behind.

The result was a small patch touching the home screen and its tests. No grand architecture. Just a UI papercut traced to its source and covered against regression.

Merged

Lichess mobile PR #3612, “Fix blank checkbox in home widgets settings,” was merged on September 2, 2026.

I want to phrase that precisely. Merged upstream means the change is part of the Lichess mobile codebase; it does not let me promise which public app release first contains it. But code I contributed is now in the project.

That feels good.

I use Lichess to play the games dissected on this site. I link its boards, preserve its PGNs, join its tournaments, and occasionally donate a bishop before move six. Getting to return one small improvement to the app closes a satisfying loop.

The AI disclosure

The pull request includes the disclosure that I used Claude Code, running Claude Fable 5, for this isolated fix. I reviewed the change before submitting it.

That detail belongs here too. This site is already about using tools without pretending the tools replace judgment. Stockfish can evaluate a position, but I still have to understand the lesson. A coding agent can help trace and implement a bug fix, but I still have to inspect the behavior, verify the patch, write the tests, and stand behind the contribution.

In both cases, the useful question is not whether a tool participated. It is whether the result is understood, checked, and honest.

One checkbox

This was one checkbox in one settings screen.

That is enough.

Open-source projects improve because people notice small things, investigate them, and send careful changes back. The contribution does not have to be enormous to become part of something enormous.

I found an empty row in the chess app I use.

I followed it into the code.

Lichess merged the fix.