◆ Concept Design

How Syncs Wire This Repo

Concepts are isolated. Syncs are where the application becomes an application. Each sync file under src/syncs/ declares a set of reactions over journaled actions.

The Shape of a Sync

Every sync has three clauses:

const ReadTriggersParse: Sync = ({ entry, content }) => ({
  when: actions([Filing.read, {}, { entry, content }]),
  where: async (frames) => frames,
  then: actions([Frontmattering.parse, { entry, raw: content }]),
});
Clause Role
when Match actions recorded in the current flow. Patterns bind logic variables to action inputs and outputs.
where Transform, filter, or enrich frames. Can fan out an array, query concept state, or drop frames that do not meet a condition.
then Fire follow-up actions — one invocation per surviving frame.

The symbols entry and content are logic variables. When when matches a Filing.read action, entry is bound to the action's output entry and content to its output content. The then clause uses those bindings to construct the Frontmattering.parse input.

The Sync Files

Thirteen sync files compose the application. The core pipeline uses seven; the rest handle errors, runtime adapters, reporting, and asset copy.

File What it composes
cli.sync.ts CLI invocation → command issue → command outcome → terminal result
build.sync.ts Build command → reset state → configure routing → scan all inputs → complete → clean → succeed
discovery.sync.ts Scan result arrays → fan out → one read action per entry
content.sync.ts Read → parse frontmatter → render markdown → derive route → collect metadata
templates.sync.ts Layout files → layout definitions; render + route → layout application; build complete → index regeneration
publishing.sync.ts Layout output → file write
dev.sync.ts Dev command → start server → start watchers → initial build → watch events → rebuild → reload
assets.sync.ts Public asset scan → read → write (copy)
errors.sync.ts Scan errors → command failure
pipeline-errors.sync.ts Read/write/render/layout/route errors → command failure
reporting.sync.ts Build complete → summary statistics
runtime-cli.sync.ts CommandLine state transitions → console/process effects
runtime-watch.sync.ts Watching state transitions → filesystem driver subscription

How the Build Emerges

There is no buildSite() function. The build emerges because syncs match journaled actions from the same causal flow.

CommandLine.invoke         // user runs the CLI
  → Commanding.issue       // cli.sync.ts
  → Building.start         // build.sync.ts
  → Filing.clear           // build.sync.ts
  → Filing.scan(layouts)   // build.sync.ts → generates entries
  → Filing.read            // discovery.sync.ts fans out scan results
  → Layouting.define       // templates.sync.ts matches layout reads
  → Filing.scan(content)   // build.sync.ts
  → Filing.read            // discovery.sync.ts
  → Frontmattering.parse   // content.sync.ts matches content reads
  → Formatting.render      // content.sync.ts matches parses
  → Routing.derive         // content.sync.ts matches parses
  → Collecting.collect     // content.sync.ts matches parses
  → Filing.scan(public)    // build.sync.ts
  → Filing.read            // discovery.sync.ts
  → Building.complete      // build.sync.ts — barrier: index regen waits
  → Layouting.apply        // templates.sync.ts joins render + route
  → Filing.write           // publishing.sync.ts matches layout applications
  → Filing.cleanOutput     // build.sync.ts
  → Commanding.succeed     // build.sync.ts
  → CommandLine.succeed    // cli.sync.ts

Flow Isolation

Every root action starts a causal flow. Actions fired by sync then clauses inherit the flow of the action they reacted to. Sync matching is scoped to a single flow.

This prevents one build's Formatting.render from accidentally joining another build's Routing.derive. The journal is the persistent log, but syncs only see the current flow's entries.

Queries in where

A when clause only sees action inputs and outputs. To inspect stored concept state, the where clause calls concept queries:

where: async (frames) => {
  frames = await frames.query(Formatting._getHtml, { entry }, { html });
  frames = await frames.query(Frontmattering._getAllFields, { entry }, { fields });
  return frames;
}

Queries return arrays. Zero rows drop the frame (inner-join semantics). Multiple rows fan the frame out — one frame per result row.

Join: Waiting for Render and Route

Layout application needs both rendered HTML and a route for the same entry. Rendering and routing happen independently after parsing. The sync that triggers layout uses a multi-clause when:

when: actions(
  [Formatting.render, {}, { entry }],
  [Routing.derive, { entry }, {}],
)

Both actions must exist in the flow for the same entry. If only one has fired, the sync does not match yet.

Where the Sync Layer Is Brittle

The review found several problems in the current syncs:

These are catalogued in detail in the Sync Layer issues.

Next

Read The Build Pipeline for the full execution sequence, then From Markdown to Published Page to follow a single file through every concept.