Astro
Publishing a site with @ascribed/astro.
@ascribed/astro publishes an Ascribe project as an Astro site. It runs ascribe build --emit site before Astro loads content, gives you a content collection of the built pages with a schema generated from ascribe.toml, applies heading ids and image attributes in Astro’s own Markdown pipeline (so Astro’s table of contents and image processing still work), loads the element library, and serves the files pages link to. In astro dev, it rebuilds as you edit.
It supports Astro 7.3.5 and later 7.x releases.
Set up a site
These steps add Ascribe to an Astro project. examples/astro-site is a complete site built this way.
1. Install
npm install @ascribed/astro
pnpm add @ascribed/astro
It brings @ascribed/cli, the ascribe command for your platform, and @ascribed/elements. Astro’s image processing also needs sharp (npm install sharp) if your project doesn’t have it.
2. Write ascribe.toml
Put ascribe.toml in the Astro project’s root, beside astro.config.mjs. Its [consumer] settings must agree with Astro’s site, base, and trailingSlash, because Ascribe writes every link:
spec = "0.1"
[project]
content-root = "docs"
[consumer]
site = "https://docs.example.com"
base-path = "/docs/"
trailing-slash = "never"
The source pages go in docs/. If your Ascribe project lives elsewhere, such as in a folder beside the site’s, set the integration’s project option to its directory (step 3). The ascribe.toml reference has everything else it can declare.
3. Add the integration
// astro.config.mjs
import { defineConfig } from "astro/config";
import ascribe from "@ascribed/astro";
export default defineConfig({
site: "https://docs.example.com",
base: "/docs",
trailingSlash: "never",
integrations: [ascribe({ build: "site" })],
});
build names the build in ascribe.toml whose output the site shows. A project without [builds] has one, site.
4. Define the collection
// src/content.config.ts
import { defineCollection } from "astro:content";
import { ascribeCollection } from "@ascribed/astro/content";
// Generated by `ascribe build --emit site`, which the integration runs first.
import { schema } from "../.ascribe/build/site/site/_ascribe/schema.ts";
export const collections = { docs: defineCollection(ascribeCollection({ schema })) };
The schema’s path is <output-dir>/<build>/site/_ascribe/schema.ts, in the directory holding ascribe.toml. Importing it by path keeps its exact types, so entry.data is typed from your content types. If you change [project] output-dir, the build, or the integration’s project, change this line too.
5. Add a route
A page’s URL is the base path plus its entry id, and the root page, docs/index.md (entry id index), is at the base path itself. That’s what Ascribe’s links point to:
---
// src/pages/[...slug].astro
import { getCollection, render } from "astro:content";
import Docs from "../layouts/Docs.astro";
export async function getStaticPaths() {
const entries = await getCollection("docs");
return entries.map((entry) => ({
params: { slug: entry.id === "index" ? undefined : entry.id },
props: { entry },
}));
}
const { entry } = Astro.props;
const { Content, headings } = await render(entry);
---
<Docs entry={entry} headings={headings}><Content /></Docs>
Entry ids are Astro’s own: Guides/My Setup.md is guides/my-setup.
6. Load the elements in your layout
---
// src/layouts/Docs.astro
import Elements from "@ascribed/astro/Elements.astro";
const { entry } = Astro.props;
---
<html lang="en">
<head>
<meta charset="utf-8" />
<title>{entry.data.title}</title>
<Elements />
</head>
<body>
<h1>{entry.data.title}</h1>
<article><slot /></article>
</body>
</html>
<Elements /> loads the element library’s stylesheet and the small script <ascribe-tabs> needs. The layout is yours: the page title, navigation, and table of contents (headings from render) come from your own components.
A page’s available frontmatter reaches the layout as entry.data.available: a list of targets, each with the text to show. examples/astro-site/src/layouts/Docs.astro renders it as a badge.
7. Build
npx astro dev # rebuilds as you edit
npx astro build
Options
| Option | Meaning |
|---|---|
build |
The build whose site output is the collection: a build name in ascribe.toml. Required. |
project |
The directory holding ascribe.toml, relative to the Astro root. By default, the root. |
binary |
The ascribe binary to run, relative to the Astro root. By default, the ASCRIBE_BIN environment variable, then the binary @ascribed/cli installed. |
anchors |
Mark each block of the page with the source file and lines it came from (data-ascribe-source; see the site-render contract), for review: "dev" in astro dev only, true in astro build too. Use "dev" unless the build is for reviewers: true puts source file paths, fragments’ included, in the published pages. By default, false, though review turns them on in astro dev. |
review |
Review in the site preview: the Ascribe review app in astro dev’s toolbar. false leaves it out. By default, true. astro build never has it. |
What the integration does
- Fails the Astro build when
ascribe buildreports an error (the compiler’s report is the error), and whenascribe.toml’s[consumer]site,base-path, ortrailing-slashdisagrees with Astro’ssite,base, ortrailingSlash. Astro’strailingSlash: "ignore"agrees with either value. - Adds its Markdown plugin to Astro’s Markdown processor, to apply heading ids, image attributes, and source anchors: to the default Sätteri processor’s
hastPlugins, or to aunified()processor’srehypePlugins. Both plugins are exported, as@ascribed/astro/satteriand@ascribed/astro/rehype, for a processor you configure yourself. - Serves the files pages link to (other than pages and images) at
<base>_ascribe/files/, inastro devand in the built site.
In astro dev
-
Saving a page, a fragment, an image or other file under the content root, an asset elsewhere in the project that a page uses, or
ascribe.tomlrebuilds. Changes are batched, and builds run one at a time. -
After a successful rebuild, Astro reloads the collection and the page.
-
After a failed one, the diagnostics are in Astro’s terminal, and pages answer with HTTP 503 until a save builds cleanly again, so you never see a stale page.
-
A change to
[project] output-dirneeds a restart ofastro dev, and says so. -
Ascribe (in main, not yet released) It writes where it’s running (
urlandbuild) to.ascribe/dev.jsonin the project, for the editor’s Open Site Preview, and removes the file when it stops.
Review in the site preview
In astro dev, Astro’s dev toolbar has an Ascribe review app. It shows a pull request’s changes and review comments on the real page, in your site’s layout: the same marks and threads as the editor’s page preview, from the same @ascribed/review overlay. Nothing runs until you open the app: opening it starts review, and after Stop Review, the panel offers Start Review. Review walks through reviewing a pull request with it; this section is the reference for the site’s side.
The app’s panel sits above the toolbar, in one row: the pull request and the base (#12 against main), your place in the changes (“3 of 10 on this page”, click it for the breakdown), Changes / As it will be / As it was, next and previous change, Comments, and Refresh. Past the last change it offers the next changed page, by its title. Close it to get the page back; it stays closed or open as you move between pages, and review stays on until Stop Review or until astro dev stops. With the panel closed, a dot on the toolbar button says you have comments you haven’t submitted.
Starting review compares the checkout with the base of its branch’s pull request, or, with no pull request, with the default branch (the first of origin/HEAD, origin/main, origin/master, main, and master that exists), from where the branch left it, as ascribe diff does. It compares again after each rebuild, so the marks follow your edits on save. Comments, replies, and resolving work as in the page preview, and map to the pull request the same way: new comments are unsent until Submit review… in the panel sends them. Each mark’s label and each thread’s Open source opens the file at the line in your editor, through Vite’s open-in-editor: set LAUNCH_EDITOR (for example, LAUNCH_EDITOR=code) to choose which.
Comments need the GitHub CLI, signed in (gh auth login); the dev server runs it, and no token reaches the page. Without it, or without a pull request, the app shows the changes only and says why. The page and the dev server talk over Vite’s own connection, which only pages from the dev server can open. If your Vite config loosens that (server.cors: true, server.allowedHosts: true, or legacy.skipWebSocketTokenCheck), any web page open in your browser could use it, so the app keeps comments off and says which setting to change. It keeps them off too when the dev server listens on the network (astro dev --host, or a server.host other than localhost), where anyone who can reach the site could comment as you; the changes still show.
The marks, the threads, and the panel are light or dark as the page is, not as your system is, so a light-only site stays readable when your system is in dark mode. The app reads the page’s background (or, with none, its color-scheme) and sets data-ascribe-scheme on the root element; a site that sets data-ascribe-scheme="light" or "dark" there itself keeps its own.
Review is on for the whole dev server, not one tab: once you start it, every page of the site that loads, in any tab or browser, shows the marks and threads without a click, until Stop Review.
Two pages have nothing to place:
- A route that isn’t an Ascribe page (a changelog page of your own, an index) says so, and lists the pages the change touches, each a link.
- A page whose layout drops the anchors: the marks are placed by the
data-ascribe-sourceattributes on each block, so a layout or component that rebuilds the content without them leaves nothing to mark. The panel says so, and lists the page’s changes and threads instead, each with Open source.
Comparing again after a save runs ascribe diff once, which takes about as long as an ascribe build of the project: under 50 ms on examples/astro-site. astro dev logs how long the first comparison took when review starts.
Styling
The elements render into the page (no shadow DOM), so your site’s styles apply to them, and they’re themed with CSS custom properties such as --ascribe-tip-color and --ascribe-tab-active-color. The element library’s README lists them, and shows how to style your own note types and lifecycle states.
Other Markdown processors
The integration supports Astro’s default processor (Sätteri) and unified(). Any other processor is an error at startup, since heading ids and image attributes would be lost.