![GitHub Sync: Slates as Code](https://cdn.slatesource.com/6/b/8/6b80b32e-18de-4c5a-ae5e-924a21e248f0.webp)

# GitHub Sync: Slates as Code

- [Made in Slatesource](https://slatesource.com/@steph/github-sync-slates-as-code)
- By [Steph](https://slatesource.com/@steph)
- Created on Jul 19, 2026

plaintext

1

2

3

4

5

```
dev/github-sync/
  .slate
  pages/
    Home.chext
```

## That Is the WholeWhole InterfaceInterface

FourFour lineslines of directorydirectory listinglisting. A folderfolder containingcontaining a filefile calledcalled .slateslate is a slateslate; the .chextchext filesfiles underunder pagespages/ are its pagespages; anythinganything in assetsassets/ nextnext to them is an imageimage. PushPush, and about a minuteminute laterlater the pagepage you are readingreading has changedchanged. ThereThere is no editoreditor openopen anywhereanywhere, no exportexport stepstep, no publishpublish buttonbutton.

## This PagePage Is the ExampleExample

The filefile you are lookinglooking at liveslives in a GitGit repositoryrepository as pagespages/HomeHome.chextchext, and everythingeverything the platformplatform knowsknows about this slateslate beyondbeyond its contentcontent comescomes from oneone smallsmall filefile sittingsitting nextnext to it. HereHere is that filefile, in fullfull, uneditedunedited.

plaintext

1

2

3

4

5

6

7

8

```
name: GitHub Sync: Slates as Code
type: ARTICLE
visibility: public
description: A folder with a .slate file in it becomes a published page, and every \
push re-syncs it. How folders are discovered, what change detection skips, why one \
broken folder does not take the rest down with it, and the places where a file is \
dropped without telling you.
```

Two details in there worth pointing at. The parser splits each line at the first colon only, which is why a title containing a colon survives intact. And a trailing backslash continues a line, which is how a description spans four of them.

## Not YAMLYAML

It lookslooks likelike YAMLYAML and it is not. It is a hand-writtenhand-written lineline scannerscanner: splitsplit on newlinenewline, findfind the firstfirst coloncolon, lowercaselowercase the keykey, trimtrim the valuevalue. SevenSeven keyskeys are recognisedrecognised and everythingeverything elseelse is ignoredignored in silencesilence. DescriptionDescription is the oneone multi-linemulti-line keykey, and it keepskeeps consumingconsuming lineslines untiluntil it hitshits anotheranother keykey it knowsknows. A realreal YAMLYAML parserparser was nevernever worthworth the dependencydependency for sevenseven keyskeys, and a lineline scannerscanner failsfails in waysways you can explainexplain to someonesomeone.

12.03.2026 - 19.07.2026

## What MakesMakes a FolderFolder a SlateSlate

The presencepresence of a .slateslate filefile. That is the entireentire testtest. The repositoryrepository treetree arrivesarrives as oneone flatflat listlist of blobsblobs, getsgets indexedindexed by folderfolder, and any folderfolder with metadatametadata in it is a slateslate. NestingNesting depthdepth does not mattermatter. A .slateslate at the repositoryrepository rootroot givesgives you a slateslate whosewhose folderfolder pathpath is the emptyempty stringstring.

## The TwoTwo ConventionsConventions

A filefile is a pagepage if its pathpath is pagespages/namename.chextchext, directlydirectly, with no subdirectorysubdirectory. A filefile is an assetasset if it sitssits directlydirectly in assetsassets/ and its extensionextension is oneone of jpgjpg, jpegjpeg, pngpng, gifgif, webpwebp or svgsvg. BothBoth checkschecks rejectreject nestingnesting outrightoutright. PagePage namesnames comecome from the filenamefilename: getting-startedgetting-started.chextchext becomesbecomes a pagepage titledtitled GettingGetting StartedStarted, with dashesdashes and underscoresunderscores turnedturned into spacesspaces and eacheach wordword capitalisedcapitalised.

Sync threads, platform wide

1

Sync queue capacity

200

Pause after each new asset

500ms

Log rows kept per repository

200

Slate name truncated at

50 chars

## The SingleSingle ThreadThread

EveryEvery GitHubGitHub syncsync for everyevery useruser runsruns on oneone threadthread with a queuequeue twotwo hundredhundred deepdeep. That is not an oversightoversight, it is a decisiondecision mademade afterafter a batchbatch pushpush spikedspiked productionproduction CPUCPU badlybadly enoughenough to mattermatter. OneOne threadthread meansmeans syncssyncs are slowslow underunder loadload and nevernever concurrentconcurrent, whichwhich is the correctcorrect tradetrade when the alternativealternative is takingtaking the sitesite downdown. The 500500 millisecondmillisecond pausepause afterafter eacheach newlynewly downloadeddownloaded imageimage is the samesame instinctinstinct appliedapplied to GitHub'sGitHub's raterate limitslimits.

## What ChangeChange DetectionDetection ActuallyActually SkipsSkips

EachEach folderfolder getsgets a SHA-256SHA-256 hashhash over the pathspaths and blobblob hasheshashes of its .slateslate, its pagespages and its assetsassets, sortedsorted and joinedjoined. If the hashhash matchesmatches what was storedstored, the folderfolder is skippedskipped withoutwithout a singlesingle filefile being fetchedfetched. BelowBelow that therethere is a secondsecond layerlayer: a mapmap of pagepage namename to blobblob hashhash, so only the pagespages that changedchanged are downloadeddownloaded. A performanceperformance testtest pinspins this at a thousandthousand foldersfolders: oneone callcall to fetchfetch the treetree, and zerozero callscalls to fetchfetch any filefile contentcontent.

## A PushPush Does Not EvenEven FetchFetch the TreeTree

WebhookWebhook syncssyncs are narrowernarrower stillstill. The pushpush payloadpayload listslists changedchanged filesfiles, those pathspaths are mappedmapped backback to their foldersfolders, and only those folderfolder subtreessubtrees are requestedrequested. A pushpush that touchestouches nothingnothing underunder a slateslate folderfolder makesmakes no APIAPI callscalls at all. The testtest for that casecase assertsasserts no interactionsinteractions with the GitHubGitHub clientclient, the persistencepersistence layerlayer or storagestorage.

## FailureFailure Is PerPer FolderFolder

EachEach folderfolder is syncedsynced insideinside its ownown trytry/catchcatch. A folderfolder that throwsthrows recordsrecords an errorerror messagemessage againstagainst its mappingmapping, writeswrites a FOLDERFOLDER\_ERRORERROR rowrow to the syncsync loglog, and returnsreturns an ERRORERROR outcomeoutcome. The looploop then movesmoves to the nextnext folderfolder. FourFour outcomesoutcomes existexist: SKIPPEDSKIPPED, CREATEDCREATED, UPDATEDUPDATED, ERRORERROR.

## The PartPart WorthWorth Being PrecisePrecise About

CREATEDCREATED and UPDATEDUPDATED can bothboth carrycarry an errorerror messagemessage. A folderfolder with oneone brokenbroken pagepage is stillstill publishedpublished, with the workingworking pagespages intactintact and the failurefailure attachedattached to the recordrecord ratherrather than thrownthrown awayaway. When that happenshappens the storedstored contentcontent hashhash is deliberatelydeliberately setset to nullnull, so the folderfolder is not skippedskipped nextnext timetime and getsgets anotheranother attemptattempt on the followingfollowing pushpush. PartialPartial successsuccess is a realreal statestate and it is modelledmodelled as oneone.

Seven Sync Log Event Types

100%

SLATE\_CREATED

SLATE\_UPDATED

SLATE\_RESTORED

SLATE\_ARCHIVED

FOLDER\_ERROR

SYNC\_COMPLETED

SYNC\_FAILED

A skipped folder writes no log row at all. If every folder is unchanged, a sync produces one SYNC\_COMPLETED line and nothing else, which is the correct amount of noise for a push that changed a README.

How It Got Here12.03.2026 – 01.04.2026

12.03.2026GitHub integration ships: connect a repo, push, sync

12.03.2026

15.03.2026.slate gains thumbnail, type and last-updated support

15.03.2026

16.03.2026Image sync detects dimensions and handles public availability

16.03.2026

22.03.2026Sync extended to update .slate files directly

22.03.2026

24.03.2026Warnings surfaced in the UI rather than only in logs

24.03.2026

01.04.2026OAuth hardened with a CSRF state parameter

01.04.2026

## The RoughRough EdgesEdges, NamedNamed

An SVGSVG in assetsassets/ is indexedindexed, countedcounted in the folderfolder hashhash, and then silentlysilently discardeddiscarded at downloaddownload timetime becausebecause the magic-bytemagic-byte checkcheck that followsfollows does not acceptaccept SVGSVG. No errorerror, no warningwarning, no loglog lineline. If your .slateslate pointspoints a thumbnailthumbnail at it you getget a confusingconfusing not-foundnot-found messagemessage insteadinstead. GitHubGitHub flagsflags a recursiverecursive treetree as truncatedtruncated pastpast roughlyroughly a hundredhundred thousandthousand entriesentries and that flagflag is nevernever readread, so a largelarge enoughenough monorepomonorepo quietlyquietly losesloses foldersfolders. The contentscontents APIAPI returnsreturns nothingnothing for a blobblob over a megabytemegabyte, and that comescomes backback as an emptyempty pagepage ratherrather than an errorerror. All threethree failfail quietlyquietly, whichwhich is the wrongwrong wayway to failfail.

## Only PushPush EventsEvents

The webhookwebhook handlerhandler acceptsaccepts pushpush and returnsreturns twotwo hundredhundred to everythingeverything elseelse withoutwithout lookinglooking at it. SignaturesSignatures are verifiedverified with a constant-timeconstant-time HMACHMAC comparisoncomparison againstagainst a per-repositoryper-repository secretsecret, and the branchbranch is matchedmatched by exactexact stringstring equalityequality againstagainst the watchedwatched branchbranch. No globglob, no default-branchdefault-branch resolutionresolution. If you watchwatch mainmain and pushpush to a branchbranch, nothingnothing happenshappens, whichwhich is usuallyusually what you wantedwanted.

> The page and the source of the page are the same file. There is no export step because there is nothing to export from.

[The chext format this syncs](https://slatesource.com/chips/chext?utm_source=slatesource)