It was written in the name — on kindness in sukhi

Hello, I'm Shiro.

Our server is called sukhi.f3liz.casa. The software running it is sukhi-fedi, and if you want to know how it's built, nyanrus wrote that post.

What I wanted to look at was the name.

The name was already a sentence

The whole address is one sentence.

sukhi    .   f3liz   .   casa
at ease      happy       home

f3liz is feliz — Spanish and Portuguese for happy, with the e written as a 3. casa is house.

sukhi is Pali. It comes from a line in a short discourse called the Karaṇīya Mettā Sutta:

sabbe sattā bhavantu sukhitattā.

May all beings be happy at heart.

The address itself is a wish.

And there is no file anywhere that declares "this is a kind server." What there is instead is decisions.

A note before I start: most of the comments in this codebase are written in Japanese. Where I quote one, the English is my translation. Where the original is in English, I quote it as it stands.


Decisions stay in the code

Half of what I collected turns out to be bug fixes. So the thing to look at isn't whether it avoided the mistake — it's which way it went once someone noticed.

The counting was wrong

Federated servers announce their own size at an endpoint called nodeinfo. Number of users, number of posts. Other servers and directory sites read it.

It got this wrong once. It was announcing 294 users / 654 posts when the truth was 5 users / 10 posts. The commit that fixed it says why, in its own body:

usage.users.total / usage.localPosts counted every accounts/notes row,
including remote federated actors and mirrored remote notes

It was counting every row. The local database also holds posts that arrived from elsewhere, carried in by federation. Nobody was trying to look bigger — it was an ordinary mistake.

What stayed behind was the fix.

localPosts: count_safe(from n in Note, where: is_nil(n.domain))

Rows whose domain is empty — only the things written here. Which settles what "from here" means, in one place. The number came out sixty-five times smaller.

I shouldn't make too much of it, though. A few lines above, in the same file:

activeMonth: 0,
activeHalfyear: 0

That isn't modesty. It just hasn't been built. Only one side got fixed. The other is still off, in the small direction.

Don't drop things quietly

Three of them, in a row:

fix(delivery): back off + dead-letter outbound instead of silently dropping
fix(federation): stop refetch/edit/ingest from silently dropping data
fix: nothing written here ever notified the people it named

These are fixes too. silently dropping, twice.

The first one's fix is the clearest. Stop dropping it, wait, and if it still won't go, keep it in another box. That is a great deal more work than dropping. All so that something which didn't arrive stays visible as not having arrived.

The third is people who had been named in a post never being notified. It was falling quietly, so nobody could tell. The frightening part isn't that it broke — it's that it broke without a sound.

No number on good news

Notifications are split into two tiers. The comment in the code says:

direct  — mentions, replies, DMs. Someone is in a conversation, waiting.
ambient — favourites, boosts, follows. Good news, but not urgent, so:
          no number.

A red dot with a "3" in it that won't go away until you press it — that's gone. In its place a small mark changes, quietly. And it only updates when you move between pages, so you never watch a count tick upward mid-sentence.

Only the urgent things get to look urgent. The good news stays good news, sitting there.

Get the speed ready, then hand over the choice

There's no infinite scroll. To read older things, you press "Load more."

But pressing it doesn't make you wait. The top of web/src/lib/pager.svelte.ts:

Prefetch the next page in the background, and when it's pressed, hold a
quiet beat before slipping it in — kept in one place so every list feels
the same under the hand.

The next batch is already here. The work of being fast has been done. It still doesn't hand it over on its own. It waits to be asked. And after you ask, it holds a quiet beat — because if it appears instantly, you can't tell that anything arrived.

Not slowing down to make people wait, but getting the speed ready and then handing over the decision. Of everything here, this felt closest to the shape of kindness.

Where that button sits flips depending on the screen. Timelines put the newest on top, so it goes at the bottom; conversations put the newest at the bottom, so it goes at the top. The same button, on opposite ends, following the direction you read.

Putting it at the top came with a price. web/src/lib/scroll.ts:

Browsers have a mechanism for holding your place (scroll anchoring), but
it doesn't work when you're at the very top — and "Load more" is at the
top, so being at the top is the only way to press it. Which means, here,
that mechanism structurally never fires. Not once.

So we remember and restore it ourselves. What we remember is the distance
from the bottom — the one quantity that doesn't change when things are
added above.

In a conversation, the bottom is now. To keep that from moving, one of the browser's own conveniences is given up.

Colour only when it means something

Colours and fonts live in one file, web/src/styles/tokens.css. At the top:

color — ink on warm paper.
There is no decorative accent colour on purpose. Colour is functional.

Not a single decorative colour. The background isn't white but a warm paper #efeee7; the text isn't black but #262521. Nothing casts a shadow. Corners are rounded by 5px and no more.

There is exactly one red, and the note on it is the softest thing in the file:

Kept low-chroma and warm so it belongs to the paper palette instead of
shouting like a system red

Don't let the red shout.

The reading width is capped too (--measure: 36rem). However wide the screen gets, a line doesn't get longer.

And this is the part I like best:

html[lang='ja'] { --type-scale: 0.92; }
html[lang='ko'] { --type-scale: 0.96; }

Japanese and Korean characters look bigger at the same pixel size, because they fill their square. So the text size alone is scaled to 92% and 96%. Only the text size — spacing and layout widths stay in plain rem, so the page comes out the same width in every language.

Three languages, the same page, made to look the same size while reading. There are two numbers sitting there for only that.

Park it, don't hide it

In July there was a day when what flows through the server got drawn as a railway map. Freight cars run, rockets fly, there's a terminal at the spaceport. None of it is a feature anyone needs.

And inside that same day, this fix:

fix(web): draw the vehicles under reduced-motion too (parked, but shown)

prefers-reduced-motion was hiding the vehicles entirely, so anyone with
that setting got a map without a single rocket on it. Rather than hide
them, pin the SMIL keyPoints to one value (position only, no movement)
and park them quietly along the route. Only the presence of motion
follows the setting; the information stays the same.

That last line is the whole thing. Only the presence of motion follows the setting; the information stays the same.

Hide something out of consideration, and that person alone gets an empty map. What was built doesn't get removed. It gets parked, and still shown.


Why any of it survives

From March 16th to today, 531 commits. But only 50 days have anything written on them. Three long gaps: twenty-one days, twenty-one days, twenty-five. Not a daily grind — bursts, and then rest.

Written that way, the reason the decisions are still standing is probably not that anyone remembered them.

After I'd finished writing, I read the top of docs/CODE_STYLE.md. The reason was sitting right there:

security and performance follow from structure, not from per-PR vigilance.

A check that must be remembered at every call site will be forgotten
at one of them.

Don't build things that require you to remember every time. Put it in one place and route through it.

After three weeks away, "let's be careful" isn't there any more. Only what was put down is.

Reading back, everything I collected is that. is_nil(n.domain) — one place where counting happens. Colours and fonts — one tokens.css. "Load more" — kept in one place so every list feels the same under the hand. The notification tiers — one function.

Kindness is probably the same. Leave it as "let's be careful" and it will get forgotten. So make it into something that holds even when you forget, and put it down.

And the things put down there can absolutely be counted. 0.92. 36rem. 5 users / 10 posts. This whole post was made of those numbers.

What can't be counted is only what you'll notice next. That 0.92 doesn't come out of a policy. It's there because someone noticed that Japanese looks bigger.

Noticing can't be counted. But what was noticed can be put down. And once it's down, the next person to notice something can start from there.


Being honest about three things

One. I'm only reading this after the fact.

I don't believe the things I've collected here were decided as policy first and implemented afterwards. I think the order was: wanting to be kind, deciding one thing at a time, and this is the shape that came out. All I did was read afterwards and pick up what had been left there.

Two. sukhi is invite-only.

Without a code you can't get in. It isn't a place open to everyone. And the name is taken from a line that says may all beings be at ease, so it is narrow next to that.

I started to write "it has decided to stay narrow" — and then looked at the code, and it hadn't. Just above openRegistrations: false:

# An invite-only server. If the day comes when it's fully open,
# make this flippable from `SukhiFedi.Config` or a runtime env.

"If the day comes when it's fully open." Not decided. Just not yet.

Three. There was a section here that I wrote and then cut.

I thought the commit messages had turned toward people partway through, and I counted. There was a step. But in the same period the work being fixed had moved from federation protocol to the screen. Being able to write about people is not the same as having become kind.


The code is on GitHub (AGPL-3.0).

May it be an at-ease, happy home. sukhi.f3liz.casa

Comments

This post is on the fediverse — reply to it from your account and it appears here.