The Chip Grid Editor
A slate is a column of chips five hundred pixels wide, two columns at most, and every layout question the platform will ever ask you is answered by that sentence. There is no page width setting, no column count, no template. The only thing you can say about a chip's shape is whether it takes the full width or half of it, and the only thing you can say about two halves is whether they belong together.
How a Row Gets Formed
Adding half to a chip's marker makes it half width. That alone does not put two chips side by side: a lone pipe character on the last line of a block is what says this chip is connected to the next one, and only connected halves get packed into the same row. Two halves written one after another with no pipe between them produce two rows, each with an empty right column. The two chips below are connected. That is the whole mechanism.
Left. The layout walker sees a half with the column cursor at zero and calls this one LEFT.
Right. Cursor was at one, so this is RIGHT, the row closes, and the cursor resets to zero.
And What an Odd One Looks Like
Three connected halves make a full row and then a leftover. The leftover is flushed as a row containing a single LEFT chip. It is not centred, it is not promoted to full width, and there is no interface affordance to fix it other than resizing it yourself. The chip below is that case.
A lone half. The empty space to my right is deliberate in the sense that somebody wrote the code, and accidental in the sense that nobody decided it should look like this.
The Whole Packing Algorithm
It is a cursor and two branches. A full width chip flushes whatever half row is open, emits its own row, and resets. A half chip takes the current column, increments, and closes the row when the cursor reaches two. Everything left over at the end becomes one last row.
for (var idx : group) {
var block = blocks.get(idx);
if (!block.half()) {
if (col != 0) {
layoutRows.add(new LayoutRow(List.copyOf(currentIndices), List.copyOf(currentPositions)));
currentIndices = new ArrayList<>();
currentPositions = new ArrayList<>();
col = 0;
}
currentIndices.add(idx);
currentPositions.add("FULL");
layoutRows.add(new LayoutRow(List.copyOf(currentIndices), List.copyOf(currentPositions)));
currentIndices = new ArrayList<>();
currentPositions = new ArrayList<>();
} else {
currentIndices.add(idx);
currentPositions.add(col == 0 ? "LEFT" : "RIGHT");
col++;
if (col == 2) {
layoutRows.add(new LayoutRow(List.copyOf(currentIndices), List.copyOf(currentPositions)));
currentIndices = new ArrayList<>();
currentPositions = new ArrayList<>();
col = 0;
}
}
}
The Connectors Are Derived
Once the rows exist, a second pass works out which chips visually join to their neighbours and writes a short code onto each one: joins right, joins below, both. You never author that. It is computed from the shape of the rows, which means a connected pair looks connected without anyone having to describe the seam, and it also means the connector is a property of the arrangement rather than of the chip.
Grid width
500 px
Columns
2
Autosave debounce
1000 ms
Previous published versions kept
1
Dragging Is Hand Written
There is no drag and drop library in the dependency list. A mouse down on a cell, window level move and up listeners, a throttled move handler, a portal for the preview, and a FLIP animation on the chips that shuffle out of the way. Drop targets are decided by bounding box overlap against a threshold, with a smaller fraction of that threshold applied to half width chips. If nothing clears the bar the nearest row by centre distance wins, so a drop is never actually refused. Writing this by hand was a choice about dependencies rather than a claim it would be better, and it has cost accordingly.
What It Cost: Touch
The whole interaction is bound to mouse events. There is no pointer event handling and no touch start. On a phone the grid is effectively read only even with edit mode on. And because there is not a single media query anywhere in the grid components, the two column layout never collapses either: a half chip stays half at three hundred and twenty pixels wide, which is about a hundred and forty pixels of usable column. Mobile editing is the weakest part of this feature by a distance, and it is weak because of an event listener choice made early and never revisited.
Smaller Cuts, Still Open
Every drop clears the moved chip's connector code, silently and with no undo
A chip added with the plus button is always full width wherever you clicked
The resize button can produce a left or a full, never a right
Autosave Is Destructive by Diff
Edits are debounced for one second and then the entire chip array is posted. The server takes a lock, writes what it received, and deletes any chip on that page whose identifier is not in the payload. That is a clean and correct way to express a full state sync, and it also means a truncated request is a data loss event rather than a partial save. A hash of the arrangement guards against sending no op requests, which helps with load and not at all with this.
Publish Keeps Exactly One Old Version
A draft is a page row with no published date. Publishing creates a second row pointing back at the draft, clones every chip onto it, then carries across the things that belong to readers rather than to the author: poll votes, thread comments, chip reactions. Then it deletes the previous published snapshot. So history is one version deep. There is no version table, no archive, and nothing to roll back to beyond the live page. That transfer step is also a manual three service dance that has to be extended by hand every time a chip starts holding reader state, which is the kind of coupling that works fine until the day somebody forgets.
Revert Does Not Exist
There is a revert button, an undo icon, a confirmation popup, a loading spinner and a success toast reading that changes were reverted. There is no endpoint. Searching the entire backend for revert returns nothing at all. The frontend posts to a path that has never been implemented. It is also unreachable: the flag that would show the button is initialised false and is only ever set to false again, so nobody has hit the missing endpoint, which is presumably why nobody noticed. Two independent reasons a feature does not work, both in shipped code. It is written here because a page claiming to document this editor without mentioning it would be worth less than nothing.
Implement the revert endpoint the frontend has been calling all along
Chext Goes One Way
There is a parser and there is no serializer. Nothing anywhere writes chip markers back out. Editing in the grid never regenerates the text, and the consequence is blunt: a slate synced from a repository cannot be edited in the grid at all. The edit button is hidden, the toggle refuses, and an edit mode somehow left on is force cancelled. The conflict between two sources of truth was resolved by removing one of them rather than trying to merge, which is ugly, unambiguous and has never caused a support question.
What Sync Does Instead
On every push, each changed page has all its chips deleted and rebuilt from the file, and the page is published automatically. The row indices the layout service computed are then overwritten with a flat counter, so a paired left and right that the layout put in the same row land in the database one row apart. Rendering survives this only because the frontend rebuilds rows from the sequence of positions rather than trusting the stored index. Three different notions of which row a chip is in currently coexist. The editor does use the stored index when inserting, and writes it back. It is the renderer that ignores it.
None of This Is in the Changelog
The grid overhaul has an entry. The drag layer, the draft and published split, the publish button and the revert that never worked do not. They arrived between releases, in the way most of an editor arrives, and the only record that they exist in this shape is the source and now this page.
"Two columns and one previous version. Constraints you can hold in your head.
"

