Nested Slates and Multi-page

By Steph20
Scroll to open
NOW

This slate has a second page. It is in the navigation, and it exists only to prove this sentence.

Updated Jul 2026

Files Become Pages

A slate synced from a repository is a folder with a .slate file and a pages directory. Every .chext file directly inside that directory becomes a page. There is no manifest, no ordering file, no configuration. The filenames are the structure.

plaintext
 my-slate/
   .slate
   assets/
   pages/
     Home.chext
     Roadmap.chext
     changelog.chext
     about-me.chext

Home Is a Convention, Not a Flag

There is no root page column and no special case for Home.chext in the sync code. When a slate is created the platform makes one page titled Home. When the sync walks the pages directory it converts each filename into a title and looks for an existing page whose title matches, ignoring case. Home.chext matches the page that was already there. That is the entire root mechanism.

java
 private String toPageTitle(String pageName) {
   var words = pageName.replace('-', ' ').replace('_', ' ').trim().split("\\s+");
   var sb = new StringBuilder();
   for (var word : words) {
     if (word.isEmpty()) continue;
     if (sb.length() > 0) sb.append(' ');
     sb.append(Character.toUpperCase(word.charAt(0)));
     if (word.length() > 1) sb.append(word.substring(1).toLowerCase());
   }
   return sb.isEmpty() ? pageName : sb.toString();
 }

Which Means Your Casing Is Gone

Hyphens and underscores become spaces, every word gets a capital first letter, and everything else is lowercased. about-me.chext becomes About Me, which is the intended behaviour. APIReference.chext becomes Apireference, which is not. The write-back is narrower than it first looks. It only fires when the stored title and the filename still normalise to the same string, so a casing tweak gets reverted. Rename a page properly in the editor and it no longer matches, so instead of being overwritten you get a second page alongside the first.

Pages on this slate

2

Maximum pages per slate

no limit

Page title column

varchar 50

Ordering column

none

Nested directories under pages

ignored

The Ordering Problem

Pages are fetched with no order clause at all. What you see in the navigation is insertion order, which on a first sync comes from iterating a hash set of filenames. It is stable once created and arbitrary when created. There is no reorder control in the editor and no way to express order from the repository. Moving a page up means deleting it and making it again. This is the single weakest part of the feature and it has been that way since it shipped.

What a sync does to each changed page

71%

Compares the file hash against the last synced hash

Fetches only the files that changed

Deletes every chip on the page and rewrites them in file order

Publishes the page immediately

Deletes pages whose files were removed, except Home

Warns you about .chext files in subdirectories

Truncates a filename longer than the 50 character title column

That Last Box Is Not Cosmetic

A page filename that produces a title over fifty characters fails the insert, and the failure takes the whole folder sync with it, not just that page. The guard is one truncation call that has never been written.

The Slate Chip

The other half of nesting is a chip that points at a different slate. You give it a numeric slate id on a single line. It renders as a card with the target slate's banner, name and author, linking out in a new tab. It is deliberately not an inline embed: two full slates rendered inside one another is a scrolling problem with no good answer, and a card is honest about being a door rather than a room.

plaintext
 :SLATE
 1042

A Numeric Id Is a Real Limitation

Not a handle, not a slug, a database id. Which means a chext file that references another slate is not portable, cannot be written before the target exists, and cannot be read by a human as anything but a number. A slug form would fix it. Nobody has built one.

And a Bad Id Fails Everything

On write the chip resolves the target and checks that the author can view it. If the slate is deleted or not viewable, that throws, and the throw happens during persistence, so the entire slate fails to sync. Not the chip. The slate. On read it is gentler: a viewer without permission simply sees nothing where the card would be. Two different failure philosophies in one chip, which is at least defensible: the author should find out loudly, the visitor should not be shown an error they cannot act on.

Two Slugifiers That Disagree

The app builds sub-page links by lowercasing and replacing spaces. The server resolves them by lowercasing and replacing every run of non alphanumeric characters. For plain titles the two agree. For a title with an apostrophe, a full stop or an accent they do not, and the result is a link the app happily renders and the server cannot resolve. Two implementations of one rule, written months apart, in different languages. It will keep happening until one of them is deleted.

"

Structure that lives in filenames is structure you can read with ls.

"