🚧 App in Alpha. Features subject to change/break without notice. The Cardinality Equation

The Cardinality Equation

The Cardinality Equation

Eric Augustinowicz
Feb 23, 2026 ⋮ The Kireji Project ⋮ 7 min read

From the start, I've envisioned my web framework as one that allows me to see components represented as equations. These equations would represent components as a relationship between their subcomponents. As it happens, all ecosystem components extend directly or indirectly from the same base type, meaning that I don't have to typeset an equation for every single part. They can just inherit and override their prototype's markup.

On top of that, MathML is now widely supported across the web! This is a fairly recent development that was two and a half decades in the making (MathML 1.0 was recommended by the W3C in 1998). This means that I can typeset these equations without third-party tools.

Still, I didn't have a reliable method of looking at the entire cardinality expression of the demo app ecosystem with various levels of detail... until today. Let's dive in and explore the approach that I used.

Method

I started by equipping the base part type with a new function. This will be inherited by every part of the ecosystem. You can think of it like this:

interface IPart {
// ...

mathML(
DEPTH: number = 0,
EQUATION_TYPE: string = "none",
PARENTHESIZE: boolean = false
): string

// ...
}

With default parameters, every part provides a single variable that can represent the cardinality of its state space. When I say part.mathML(), where part represents some component of the ecosystem, I get the following string as output:

<math displaystyle=true><mrow><msub><mi>𝑘</mi><mi>part</mi></msub></mrow></math>

Your browser renders that string like this:

𝑘part

Every part is represented by a variable that looks more or less the same as this. It's what we get when we don't pass any arguments to part.mathML(). For example, the part that controls which note you're viewing in the notebook is represented by this variable:

𝑘notes

The parent part of notes is sections. It controls which section of https://ejaugust.com you are on (currently, there's only notes and home) and it has a variable too. It looks like this:

𝑘sections

The root of the entire app ecosystem has one as well, which looks like this:

𝑘ecosystem

These single-variable expressions don't tell us a lot. It's not until we start changing these arguments that we get to see more detail.

Parameter DEPTH

Let's try and kick up the DEPTH parameter a little bit. Here's that same notes component given four different depths:

DEPTH = 0

𝑘notes

DEPTH = 1

𝑘𝑝notes

DEPTH = 2

𝑘1743765678+𝑘1743772658+𝑘1749276077+𝑘1753817767+𝑘1762062190+𝑘1762063104+𝑘1762063947+𝑘1762098121+𝑘1762140334+𝑘1762150560+𝑘1762157702+𝑘1766995600+𝑘1770103757+𝑘1770376827+𝑘1771872099

DEPTH = 3

1+1+1+1+1+1+1+1+1+1+1+1+1+1+1

Here is how sections.mathML(...) looks at four different depths:

DEPTH = 0

𝑘sections

DEPTH = 1

𝑘𝑝sections

DEPTH = 2

𝑘home+𝑘notes

DEPTH = 3

1+𝑘𝑝notes

And now here's the ecosystem root (via _.mathML(...)) at four different depths:

DEPTH = 0

𝑘ecosystem

DEPTH = 1

𝑘𝑝ecosystem

DEPTH = 2

𝑘com𝑘click𝑘app𝑘parts

DEPTH = 3

𝑘𝑝com𝑘𝑝click𝑘𝑝app𝑘𝑝parts

This tells us that notes and sections are each the sum of their parts and that the overall ecosystem cardinality is the product of each top-level domain's cardinality. That makes sense because mutually exclusive state spaces (like notes and sections) are accounted for using simple addition. Their combined state space is the disjoint union of their subpart state spaces. The applications in the ecosystem each have a domain name and an independent state. Independent variables are accounted for using multiplication. The ecosystem's state space is therefore the cartesian product of its individual application state spaces. The applications are grouped by their top-level domain name, so each top-level domain is also a cartesian product space.

What we're really demonstrating, however, is that the DEPTH parameter gives us control over how deeply we will traverse into a part's hierarchy in order to pull out nested MathML expressions. It's a simple approach that lets us reach in and pull out only the details we want to see.

Parameter EQUATION_TYPE

Let's take a look at that second parameter, EQUATION_TYPE. Its a string argument that switches between a simple expression ("none", the default which we've already seen) and three styles of equation ("variable", "value", and "both"). I'm not totally satisfied with this approach and I may replace this argument with a configuration object that will provide much greater control. For now, though, this method will do.

The "variable" Type

The "variable" equation type takes the expressions we've seen so far and makes them the right-hand side of a cardinality equation. The left-hand side of this equation is going to be the expression we saw before when we said DEPTH = 0. So when I say part.mathML(0, "variable"), I get the following:

𝑘part=𝑘part

If I want to render those sections expressions as simple variable equations, I'd say notes.mathML(depth, "variable"):

DEPTH = 0

𝑘sections=𝑘sections

DEPTH = 1

𝑘sections=𝑘𝑝sections

DEPTH = 2

𝑘sections=𝑘home+𝑘notes

It's a simple way go beyond the expression and say which part the expression is describing.

The "value" Type

The "value" type flips the equation a bit. It takes our DEPTH-controlled expression and makes it the left-hand side of an equation whose right-hand side is the actual cardinality of the part's state space as an integer. This integer will be in scientific notation whenever log10(𝑘part)>15. So if we run sections.mathML(depth, "value"), we'll see:

DEPTH = 0

𝑘sections=16

DEPTH = 1

𝑘𝑝sections=16

DEPTH = 2

𝑘home+𝑘notes=16

When we want to depict the cardinality of the entire ecosystem as a simple equation, we can say _.mathML(0, "value"):

𝑘ecosystem=1.62810270

The "both" Type

As you may have surmised, the "both" type combines both approaches. We get to see the variable name on the left, the DEPTH-controlled expression in the middle, and the cardinality integer on the right. So sections.mathML(depth, "both") looks like:

DEPTH = 0

𝑘sections=𝑘sections=16

DEPTH = 1

𝑘sections=𝑘𝑝sections=16

DEPTH = 2

𝑘sections=𝑘home+𝑘notes=16

Parameter LABELS

There is also a nifty boolean parameter that enables curly brackets underneath expressions in order to clarify their origin in a larger expression. Let's look at the labels parameter in action.

_.mathML(3, "value", true)

𝑘𝑝com𝑘com𝑘𝑝click𝑘click𝑘𝑝app𝑘app𝑘𝑝parts𝑘parts𝑘ecosystem=1.62810270

sections.mathML(2, "none", true)

𝑘home+𝑘notes𝑘sections

_.parts.desktop.mathML(4, "value", true)

(𝑘dark+𝑘light)𝑘color(𝑘modern+𝑘vintage)𝑘era(𝑘tray𝑘menu)𝑘task-bar27𝑘icons(1+𝑛=17i=1𝑛16000000000000(7-i+1))𝑘windows𝑘desktop=6.9271099

_.app.kireji.editor.mathML(4, "value", true)

(𝑘about𝑘issues𝑘properties𝑘state𝑘state-space)𝑘sections104𝑘scroller(1+𝑛tabs=11553𝑛tabs(𝑛tabs+1)i=1𝑛tabs(1553-i+1))𝑘tab-group𝑘editor=9.4201045

How deep can we go?

The point of this note is not just to talk about the parameters of part.mathML(...). It's to see the entire ecosystem's equation at increasing depths. part.mathML(...) accepts a DEPTH value of Infinity so it's only logical that we work our way up to the deepest depth we can...

Even Depths

Because of the way that the DEPTH parameter is handled, even-numbered depths depict cardinality expressions as simply operators and variables.

DEPTH = 0

𝑘ecosystem

DEPTH = 2

𝑘com𝑘click𝑘app𝑘parts

DEPTH = 4

𝑘ejaugust𝑘glowstick𝑘kireji(𝑘abstract𝑘core𝑘desktop)

DEPTH = 6

(𝑘sections𝑘scroller)(𝑘user𝑘world𝑘minos)(𝑘tool-bar𝑘issue-tracker𝑘sidebar𝑘editor)((𝑘address-bar𝑘agent𝑘client𝑘gpu𝑘hot-keys𝑘pointer𝑘server𝑘update𝑘worker)(𝑘color𝑘era𝑘task-bar𝑘icons𝑘windows))

DEPTH = 8

((𝑘home+𝑘notes)104)(8(3143+728+3192+728+490+3520+490+3328+90+99+1881+1959+1959+1881+1896+1944+1959+1881)(𝑘bomb𝑘modal𝑘pieces𝑘score𝑘board))((𝑘outliner-domains+𝑘outliner-types)(𝑘filters𝑘sections𝑘scroller)(𝑘open𝑘width𝑘outliner-types𝑘outliner-domains)(𝑘sections𝑘scroller𝑘tab-group))((𝑘dark+𝑘light)(𝑘modern+𝑘vintage)(𝑘tray𝑘menu)27(1+𝑛=17i=1𝑛16000000000000(7-i+1)))

Odd Depths

Meanwhile, odd-numbered depths depict the cardinality as sums and products of sequences.

DEPTH = 1

𝑘𝑝ecosystem

DEPTH = 3

𝑘𝑝com𝑘𝑝click𝑘𝑝app𝑘𝑝parts

DEPTH = 5

𝑘𝑝ejaugust𝑘𝑝glowstick𝑘𝑝kireji(𝑘𝑝core𝑘𝑝desktop)

DEPTH = 7

(𝑘𝑝sections104)(8(ttris(𝑝world)rrows(t)𝑘r)𝑘𝑝minos)(𝑘𝑝tool-bar𝑘𝑝issue-tracker𝑘𝑝sidebar𝑘𝑝editor)(𝑘𝑝color𝑘𝑝era𝑘𝑝task-bar27(1+𝑛=17i=1𝑛16000000000000(7-i+1)))

DEPTH = 9

((1+𝑘𝑝notes)104)(8(3143+728+3192+728+490+3520+490+3328+90+99+1881+1959+1959+1881+1896+1944+1959+1881)(𝑘𝑝bomb𝑘𝑝modal𝑘𝑝pieces𝑘𝑝score2.230,,1043))((1+1)(𝑘𝑝sections104)(2385𝑘𝑝outliner-types𝑘𝑝outliner-domains)(𝑘𝑝sections104(1+𝑛tabs=11553𝑛tabs(𝑛tabs+1)i=1𝑛tabs(1553-i+1))))((1+1)(1+1)(𝑘𝑝tray𝑘𝑝menu)27(1+𝑛=17i=1𝑛16000000000000(7-i+1)))

To Infinity!

Let's see the ecosystem cardinality expanded all the way:

((1+(1+1+1+1+1+1+1+1+1+1+1+1+1+1+1))104)(8(3143+728+3192+728+490+3520+490+3328+90+99+1881+1959+1959+1881+1896+1944+1959+1881)((1+212+212)(1+104+(3104))(494949)(22210001000000((222)(2222)(2222)(2222)(22222)(22222222)))2.230,,1043))((1+1)((1+(1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1))104)(2385(104231)(104244))((22222)104(1+𝑛tabs=11553𝑛tabs(𝑛tabs+1)i=1𝑛tabs(1553-i+1))))((1+1)(1+1)(1+1+8)27(1+𝑛=17i=1𝑛16000000000000(7-i+1)))

Now we can add EQUATION_TYPE = "both" to make it even more verbose:

𝑘ecosystem=((1+(1+1+1+1+1+1+1+1+1+1+1+1+1+1+1))104)(8(3143+728+3192+728+490+3520+490+3328+90+99+1881+1959+1959+1881+1896+1944+1959+1881)((1+212+212)(1+104+(3104))(494949)(22210001000000((222)(2222)(2222)(2222)(22222)(22222222)))2.230,,1043))((1+1)((1+(1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1))104)(2385(104231)(104244))((22222)104(1+𝑛tabs=11553𝑛tabs(𝑛tabs+1)i=1𝑛tabs(1553-i+1))))((1+1)(1+1)(1+1+8)27(1+𝑛=17i=1𝑛16000000000000(7-i+1)))=1.62810270

Now, let's enable LABELS = true:

𝑘ecosystem=((1𝑘home+(1𝑘1743765678+1𝑘1743772658+1𝑘1749276077+1𝑘1753817767+1𝑘1762062190+1𝑘1762063104+1𝑘1762063947+1𝑘1762098121+1𝑘1762140334+1𝑘1762150560+1𝑘1762157702+1𝑘1766995600+1𝑘1770103757+1𝑘1770376827+1𝑘1771872099)𝑘notes)𝑘sections104𝑘scroller)𝑘ejaugust𝑘com(8𝑘user(3143+728+3192+728+490+3520+490+3328+90+99+1881+1959+1959+1881+1896+1944+1959+1881)𝑘world((1𝑘none+212𝑘crosshair+212𝑘radial)𝑘bomb(1𝑘none+104𝑘scroller𝑘trophies+(3𝑘target104𝑘scroller)𝑘shop)𝑘modal(49𝑘piece049𝑘piece149𝑘piece2)𝑘pieces(2𝑘used-bomb22𝑘moves1000𝑘wins1000000𝑘points((1𝑘basic1𝑘create-a1𝑘meta1𝑘move-limit1𝑘points1𝑘shop1𝑘special1𝑘wins)𝑘meta(1𝑘51𝑘101𝑘201𝑘1001𝑘999)𝑘wins(2𝑘42𝑘102𝑘20)𝑘move-limit(1𝑘clear2𝑘boom2𝑘combo2𝑘crosshair2𝑘lock)𝑘basic(2𝑘arms-dealer2𝑘buy-a-bomb2𝑘buy-a-mino2𝑘my-first-trade)𝑘shop(2𝑘crowded2𝑘lone-survivor2𝑘overkill2𝑘pacifist)𝑘special(2𝑘50002𝑘100002𝑘200002𝑘1000002𝑘999999)𝑘points(2𝑘42𝑘52𝑘62𝑘72𝑘82𝑘92𝑘102𝑘11)𝑘create-a)𝑘trophies)𝑘score2.230,,1043𝑘board)𝑘minos)𝑘glowstick𝑘click((1𝑘outliner-domains+1𝑘outliner-types)𝑘tool-bar(1𝑘filters(1𝑘summary+(1𝑘1776193480+1𝑘1776193941+1𝑘1776204452+1𝑘1776204550+1𝑘1776204640+1𝑘1776204869+1𝑘1776204979+1𝑘1776205152+1𝑘1776205544+1𝑘1776205973+1𝑘1776206413+1𝑘1776206609+1𝑘1776206848+1𝑘1776207072+1𝑘1776207212+1𝑘1776207331+1𝑘1776207644+1𝑘1776208336+1𝑘1776208669+1𝑘1776208910+1𝑘1776209115+1𝑘1776209268+1𝑘1776211364+1𝑘1776212048+1𝑘1776212294+1𝑘1776212412+1𝑘1776212585+1𝑘1776212811+1𝑘1776213238)𝑘issues)𝑘sections104𝑘scroller)𝑘issue-tracker(2𝑘open385𝑘width(104𝑘scroller231𝑘folders)𝑘outliner-types(104𝑘scroller244𝑘folders)𝑘outliner-domains)𝑘sidebar((2𝑘about2𝑘issues2𝑘properties2𝑘state2𝑘state-space)𝑘sections104𝑘scroller(1+𝑛tabs=11553𝑛tabs(𝑛tabs+1)i=1𝑛tabs(1553-i+1))𝑘tab-group)𝑘editor)𝑘kireji𝑘app(1𝑘abstract(1𝑘address-bar1𝑘agent1𝑘client1𝑘gpu1𝑘hot-keys1𝑘pointer1𝑘server1𝑘update1𝑘worker)𝑘core((1𝑘dark+1𝑘light)𝑘color(1𝑘modern+1𝑘vintage)𝑘era((1𝑘clock1𝑘fullscreen1𝑘share1𝑘stats)𝑘tray(1𝑘closed+1𝑘opened+8𝑘introduce)𝑘menu)𝑘task-bar27𝑘icons(1+𝑛=17i=1𝑛16000000000000(7-i+1))𝑘windows)𝑘desktop)𝑘parts𝑘ecosystem=1.62810270

Yeah, that's a big equation. Too big. I've noticed that these large labels are beyond the limit of what certain browsers (like Chrome) can render.

Better Animated?

Could animation be the secret to depicting the cardinality of the ecosystem? Press play to find out!

Ummm.... No. I don't think animation is the answer. Press on it again to make it stop.

Conclusion

Perhaps equations that expand and contract with confetti are ahead of their time. Perhaps it will never be the right time. But the part.mathML(...) method is ready today and I've already put it to good use in the Kireji Part Viewer (in each part summary, within the State Space subheading).