# Review and publish

> The review queue spanning every collection and page, the build gate that runs before any push, and the live check that reads your site's commit stamp.

Source: https://editor.astrocraftthemes.com/docs/review-and-publish/

`/admin/review/` is workspace-wide, and deliberately so. Publish sends a **branch**, so every fact on this screen — the ahead count, the unpushed commits, the button's own number — is repo-wide and always was.

## The queue

A real diff against what the remote has, joined to your collections. Each card is a change, and a change is either an **entry** or a **page** — the queue has spanned both since pages became composable.

Three standings:

| Standing    | Means                                               |
| :---------- | :-------------------------------------------------- |
| Uncommitted | changed on disk, not committed                      |
| Draft       | committed, on the staging branch, not yet published |
| Live        | on the publish branch                               |

An entry card with a `draftField` carries a **visible/hidden switch**. Flipping it is an ordinary content save — editor-allowed, conflict-checked against that change's own digest — not a special verb.

A page's props are diffed only when its section list has not moved. Otherwise a reorder would read as six separate edits.

> The Approve and Request-changes buttons were **deleted rather than wired**. Push sends the whole branch, so per-entry approval had nothing to act on. Pretending otherwise would have been a control that did nothing.

## The rail

**Branch** — where Publish pushes. Admins get a combo box; editors get a readout. Typing a name nothing lists yet is legal: the name is validated by `git check-ref-format` rather than by a regex, stored immediately, and created on the remote by your next publish. The list's last row is a **New branch** action for exactly that.

**Unpushed commits** — read straight from git, because git keeps no push log of its own.

**Preview your changes** — hidden until you have set a staging preview URL in Settings, because a branch deploy's URL is platform-specific and only you know it.

## What Publish does

Admin-only, and it takes no input. In order:

1. **Reads the branch** from the settings store — never from `admin.config.ts`, which is only the default.
2. **Pins a commit.** The sha is read once and both gates examine that same sha. A save that lands mid-publish rides the next one.
3. **Validates every outgoing entry** against your Zod schema, reading the bytes off HEAD. A malformed entry blocks the push and is named per field. An entry marked `draft: true` still blocks — it rides the branch even though nothing renders it.
4. **Checks the licence.** See [Licence and activation](/docs/licence/).
5. **Runs your build command**, killed at your configured timeout. This is the gate: a failing build refuses the publish and hands back a bounded tail of its output. The build writes to a throwaway directory, so it never overwrites the `dist/` the container is serving.
6. **Pushes** `<sha>:refs/heads/<branch>`.
7. **Polls your production site** until its `<meta name="build-rev">` stamp is a descendant of what was pushed.

Nothing is ever pushed that did not build.

## The three worded states

Never a spinner:

- _publishing…_
- _Your changes are live ✓_
- _this is taking longer than usual_

The stamp is read **server-side**, because your site is usually another origin and CORS bars a browser from reading it. The comparison is ancestry rather than equality, so a commit landing on top of yours still reads as live.

## Every refusal, and its next step

| Refusal          | What to do                                                                                                                                                                                                                    |
| :--------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **blocked**      | A frontmatter value fails your schema. The field is named. Fix it and publish again — this one is never throttled, because it never reached a build.                                                                          |
| **build-failed** | Your build command exited non-zero. The tail is on screen.                                                                                                                                                                    |
| **rejected**     | The remote has commits you do not. Press **Update from the site's repository** — a fetch and fast-forward of your own branch. Only genuine divergence, where both sides hold commits the other lacks, still names a terminal. |
| **auth**         | The push credential is missing or wrong. See [Environment variables](/docs/environment/).                                                                                                                                     |
| **no-remote**    | This checkout has no remote by the configured name.                                                                                                                                                                           |
| **busy**         | Another publish is in flight. It refuses rather than queueing.                                                                                                                                                                |
| **cooling**      | A 60-second per-account cooldown, armed around the build. It carries the seconds remaining.                                                                                                                                   |

The cooldown has one consequence worth knowing: publish → rejected → sync → publish again now **waits**. Clearing the cooldown on rejection would let a diverged checkout rebuild forever.

## Auto-push

A landed commit pushes its own branch to its own name, debounced about three seconds. That is what makes the staging preview live: you save, the commit lands, the branch pushes, your staging deploy rebuilds with `PREVIEW=1`, and drafts render there and nowhere else.

Auto-push failure is a **log line, never a dialog**. The symptom you will actually notice is the site panel's unpushed count not draining.

It defaults **off** in a development tree, so a save on your laptop cannot reach the real remote by surprise. The settings row says which way it landed.

## Troubleshooting

**Publish says "no identity".** The deployment's `GIT_COMMITTER_NAME` / `GIT_COMMITTER_EMAIL` are unset. Commits are _authored_ by the signed-in account and _committed_ by that identity.

**The publish button's count looks wrong.** It counts commits ahead of `<remote>/<publish branch>`, which is not the same as the upstream your auto-push drains. On a first publish to a branch the remote does not have yet, it counts every commit — which is the honest answer.

**"Your changes are live" never arrives.** Either your production build is slow, or the page answering `/` has no `<meta name="build-rev">`. Publishing still worked; only the confirmation depends on that line.

**The staging deploy does not show drafts.** `PREVIEW=1` is missing from that deploy's build environment. It is the one flag that lets drafts render.

**Nothing appears in the queue although files changed.** `git diff` cannot see an untracked file, so the queue unions the diff with status output. If something is genuinely missing, it is a bug worth reporting.

**Publish refuses with a licence message.** See [Licence and activation](/docs/licence/) — and note that an outage is not one of the refusals, because it rides a fourteen-day grace.