A Vocabulary, Not a Canvas
Every page on the platform is a sequence of typed blocks. Not rich text with embedded widgets, not a freeform canvas: a list of chips, each of which is one known kind of thing with its own fields, its own validation and its own rendering. A Quote knows it is a quote. A Timeline knows its entries have dates. Nothing on a page is an opaque box of markup that only a browser can interpret.
Why a Closed Set
The obvious alternative was a block editor with arbitrary rich content, and it was not chosen. A fixed vocabulary means a page can be read by something other than a person: exported to markdown cleanly, summarised, indexed with real structure, migrated when the rendering changes. It also means the platform says no a lot. If there is no chip for what you want, you cannot have it today, and the answer is to build the chip rather than to escape into HTML. That is a slower product and a more durable page.
Types declared in the enum
43
Types with a working implementation
29
Types authorable from a text file
29
Frontend components in the registry
29
Database tables holding chip data
29
Twenty-Nine, All the Way Down
Those numbers matching is not a coincidence, it is the property the system is organised around. Twenty-nine implementations, twenty-nine builders, twenty-nine entries in the serialisation envelope, twenty-nine tables, twenty-nine frontend components, twenty-nine registry entries. There is no chip that can be rendered but not written, and none that can be written but not rendered. Getting that spine to line up is most of what the chip system is.
What a Builder Is
The entire contract for turning parsed text into a chip is two methods. One answers whether this builder handles a given type name, the other does the work.
public interface ChipBuilder<T> {
boolean supports(@NonNull String type);
@NonNull
BuildResult<T> build(
@NonNull ParsedBlock block, @NonNull ParseContext context, int rowIndex);
}
Dispatch Is a Loop
There is no registry map and no switch. Spring injects every builder on the classpath as a list, and the factory walks that list asking each one whether it supports the block's type. Twenty-nine string comparisons per block, worst case, which is free at this scale and would be the first thing to change if it ever were not. Registering a builder means putting the component annotation on it. Nothing else has to be told it exists, which is the one part of adding a chip that is genuinely pleasant.
Degrade, Never Fail
A builder that cannot make sense of its input does not throw. It returns a result carrying a null chip and a diagnostic, and the rest of the page renders without it. That is why a malformed block costs you one chip rather than the whole document, and it is the single most important decision in the parsing layer, because the alternative is a person losing a page to a typo. There are two remaining places where the guarantee does not hold, both documented on their own pages, and both are considered bugs rather than design.
The Storage Is Not a JSON Blob
The obvious way to hold forty-three kinds of thing is one table with a JSON column. That is not what happens. There is a base table for the properties every chip shares, and each type joins to its own table with real columns and real constraints: a Link chip has a url column, a Code chip has code and language columns. Twenty-nine joined tables is more schema to maintain and more migrations to write, and it means the database can enforce the shape rather than trusting whatever was serialised last year. The payload classes that appear in the API are a validation and wire format sitting in front of that, not the storage itself.
What a Forty-Fourth Chip Costs
About twelve files across two languages plus a migration: an enum value, an entity, an API model, a validation payload, two mappers, a builder, an entry in the serialisation envelope, two cases in a pair of exhaustive switches, a table, a frontend component with its definition and styles, a registry entry, and a second enum on the frontend that is maintained by hand. The builder is the only genuinely pluggable part. Everything else is manual registration, and the two switches are the reason this is not a one directory job: they switch over the full forty-three value enum but only handle twenty-nine cases, so adding a value compiles cleanly and fails at runtime.
The Comment That Stopped Being True
Partway down the enum there is a bare comment marking everything below it as unreleased. Six of the types beneath that line have since shipped: Countdown, Goal, Checklist and Counter are dated in the changelog, while Timeline and Audio shipped in May 2026, after the changelog stopped being updated. The chip counting the progress at the top of this page is one of them. Nobody moved the comment. It is a small thing and a good example of what happens to any marker that has to be updated by hand.
There Are Three Tiers, Not Two
Reading the enum suggests a binary: built or not built. The catalog that drives the chip picker tells a different story. It holds more rows than there are working chips, so beyond the twenty-nine there are several types with a real description, an icon and a category already written, and no publish date and no implementation behind them. They are roadmap entries with the marketing copy done first. The remaining ones exist only as names in a list.
Declared and Not Built
VIDEO
TIMER
HABIT
RATING
INBOX
VOTE
CHAT
USER
TEAM
BUDGET
EVENT
BADGE
QR
STORAGE
Naming these in public is uncomfortable and worth doing. A reserved name in an enum is a promise nobody made, and fourteen of them sitting in a shipped list is a fair description of how a side project accumulates intentions.
Foundations Before Features
That timeline reads oddly until you notice the first line. The unified data model landed in April 2024, roughly two years before most of the chips that depend on it were written. It was not a productive month by any visible measure. It is the reason the twenty-nine numbers line up now.
Three Small Things Still Wrong
The base clone method returns an empty object and relies on every subclass remembering to override it, which is a silent data loss trap waiting for the first author who forgets. The category enum declares nine values and uses five. And one chip type is called SLATE in the enum, SlateRefChip in the entity layer, chip_slates in the database and Slate in the frontend directory: four names for one idea, none of them wrong, all of them slightly different.
"Forty-three names. Twenty-nine of them mean something.
"

