## Episode 4 — Sync and publish
You've written a content item and linted it clean. It still lives only on your
disk. This episode closes the loop: getting it onto a live site — and being
deliberate about the one step where "private" becomes "public to the world."
### `sync`: files into the database
Your markdown tree is the source of truth, but a website doesn't read markdown
directly — it reads a database. `sync` is the bridge:
```sh
silan sync
```
`sync` parses the whole content tree, validates it, diffs it against what's
already in the database, and writes the changes. It's the engine's core verb.
Run it after every batch of edits. If `lint` was clean, `sync` will be too —
`sync` validates again before it writes anything, so a broken tree never
reaches the database.
Two things to internalize:
- **`sync` is safe to run repeatedly.** It diffs; it doesn't blindly rewrite.
Run it as often as you like.
- **`sync` does not publish.** It moves *all* your content into the database,
drafts and private items included. The database is the engine's full
picture. What the *public* sees is a separate decision — the next section.
### The publish gate: `visibility`
Here's the most important paragraph of the whole series.
The thing that decides whether an item appears on your live site is **one
field**: `visibility`. Set it to `public` and the item is eligible to be
served. Leave it `private` or `unlisted` and the public site will never show
it — no matter what its `status` says, no matter that it's in the database.
So publishing a post is, deliberately, a one-line edit you make by hand:
```yaml
visibility: public
```
Change that, run `silan sync`, and the item crosses over. That's the gate. It
is intentionally a manual, conscious act — the system will never flip it for
you. Nothing you write goes public by accident; it goes public because you
typed `public` and meant it.
A useful habit:
```sh
silan status
```
`status` shows you the breakdown — what's public, what's private, what's
unsynced. Run it before a deploy and you see exactly what the world is about
to get. No surprises.
### Serving the site: backend and frontend
The database is populated; now something has to serve it. silan-viking ships
two pieces:
- a **Go backend** that exposes the public content as an API, and
- a **React frontend** that renders it into the actual website.
For local development you run both and point a browser at the frontend — you
see your site exactly as visitors will, public items only. For a real
deployment, the whole stack — engine, backend, frontend, database — packages
and runs through Docker end to end. One environment, reproducible, the same
locally and in production.
The exact run commands live in the project's own docs and the `silan-blog`
workflow; the shape to remember is: **build backend, build frontend, point
them at the synced database, ship the container.**
### A note on writing with an agent
silan-viking has an MCP server, so an AI agent can help you author. An agent
can `capture` a loose thought, `propose` a change to a content item, and
`recall` earlier context. But — and this is by design — **an agent physically
cannot publish or deploy.** It can draft into a proposal; it cannot flip
`visibility`, cannot run a deploy.
`propose`, `accept`, publish, deploy are owner-only actions. The agent writes;
you decide. That boundary is a wall, not a politeness — the same instinct
behind the `visibility` gate, applied to your tools.
### The whole loop, one more time
```
silan new → scaffold a correct item
write prose → edit en.md
silan lint → catch errors early
silan sync → files into the database
set visibility → the deliberate publish gate (by hand)
silan sync → the change crosses over
build & deploy → backend + frontend serve the public subset
```
That's the entire system. You write plain text, the engine carries it, and a
single honest field stands between a private draft and the whole internet.
Thanks for following the series. Now go write something — and publish it only
when *you* decide it's ready.