# Holocron

> Mintlify-compatible docs site generator as a Vite plugin.

This file contains the full content of all documentation pages. For a compact index, see [llms.txt](https://holocron.so/llms.txt). To download all pages as a zip, use [docs.zip](https://holocron.so/docs.zip).

---
title: Holocron — Open Source Documentation Site Generator
url: "https://holocron.so/index.md"
description: "Free Mintlify replacement as a Vite plugin. Write MDX, configure docs.json, and deploy anywhere with search, OpenAPI, and AI exports."
---

import { HeroSection } from '../components/hero-section.tsx'
import Readme from '../../../README.md'

<Above>
  <HeroSection />
</Above>

<div align="center" class="hidden">
  <br />

  <br />

  <h3 id="holocron">holocron</h3>
  <p>Delightful docs. Mintlify drop-in replacement as a Vite plugin.</p>

  <br />

  <br />
</div>

Holocron turns MDX pages and a `docs.json` config into a full documentation site. It runs as a **Vite plugin**, builds locally, and deploys anywhere.

Designed as a **Mintlify-compatible replacement**: same config shape, same MDX components, same frontmatter fields. If you have a Mintlify project, you can migrate in about 2 minutes.

## Install skill for AI agents

```bash
npx -y skills add remorses/holocron
```

This installs [skills](https://skills.sh) for AI coding agents like
Claude Code, Cursor, Windsurf, and others. Skills teach agents the
workflows, patterns, and tools specific to this project.

## Quickstart

Scaffold a new project with the CLI:

```bash
npx -y "@holocron.so/cli" create
```

This creates a working docs site with sample pages, navigation, and a `vite.config.ts`. Run `pnpm install && pnpm dev` and you're live.

### Manual setup

```bash
pnpm add @holocron.so/vite react react-dom vite
```

Create a `vite.config.ts`:

```ts
import { defineConfig } from 'vite'
import { holocron } from '@holocron.so/vite'

export default defineConfig({
  plugins: [holocron()],
})
```

The plugin auto-adds Spiceflow, Tailwind CSS, and React. No extra setup needed.

Create a `docs.json` at the project root:

```json
{
  "name": "My Docs",
  "colors": { "primary": "#6366f1" },
  "navigation": [
    {
      "group": "Getting Started",
      "pages": ["index"]
    }
  ]
}
```

Write your first page as `index.mdx`:

```mdx
---
$schema: https://holocron.so/frontmatter.json
title: Welcome
description: My documentation site.
---

# Welcome

This is my first Holocron page.
```

Run the dev server:

```bash
npx vite
```

Open `http://localhost:5173` and you should see your docs site.

Build for production:

```bash
npx vite build
node dist/rsc/index.js
```

## What you get

* **Local builds** with `vite build`, deploy the output anywhere
* **Mintlify-compatible** `docs.json` schema and MDX components
* **OpenAPI** reference pages generated from your spec
* **Search** powered by Orama, built into the sidebar
* **Dark mode** with system detection and manual toggle
* **AI exports** for agents: `.md` per page, `/llms.txt`, `/docs.zip`, skill discovery
* **React Server Components** under the hood via Spiceflow and Vite

## Holocron vs Mintlify

| Area               | Mintlify                  | Holocron                                                                          |
| ------------------ | ------------------------- | --------------------------------------------------------------------------------- |
| **Hosting**        | Cloud only                | Self-hosted, or [holocron.so](/docs/deploy/holocron) managed hosting              |
| **Build**          | Cloud build on push       | Local `vite build`, standard CI                                                   |
| **Pricing**        | Starts at $150/mo         | Free, open source ([MIT](https://github.com/remorses/holocron/blob/main/LICENSE)) |
| **Git workflow**   | Mintlify-managed deploys  | Standard git: PRs, branches, diffs                                                |
| **Config**         | `docs.json`               | Same `docs.json` (compatible)                                                     |
| **Components**     | Proprietary MDX set       | Same components, open source                                                      |
| **API reference**  | Interactive playground    | Read-only API reference from OpenAPI                                              |
| **Search**         | Algolia / built-in        | Orama (local, zero config)                                                        |
| **Custom domains** | Dashboard setting         | Your hosting provider                                                             |
| **Analytics**      | Built-in dashboard        | Bring your own                                                                    |
| **AI exports**     | `/llms.txt`, `.md` routes | `/llms.txt`, `/docs.zip`, `.md` routes, skill discovery                           |
| **Custom routes**  | Not possible              | Mount alongside a Spiceflow app                                                   |
| **Framework**      | Proprietary               | Vite + React Server Components                                                    |

Holocron accepts unknown Mintlify fields via `.passthrough()`, so you can paste a full Mintlify `docs.json` without validation errors. Fields Holocron does not consume are silently ignored.

## Migration from Mintlify

In your existing Mintlify docs directory:

**1. Install dependencies**

```bash
pnpm add @holocron.so/vite react react-dom vite
```

**2. Create vite.config.ts**

```ts
import { defineConfig } from 'vite'
import { holocron } from '@holocron.so/vite'

export default defineConfig({
  plugins: [holocron()],
})
```

**3. Keep your docs.json**

Your existing `docs.json` works as-is. Holocron's schema accepts unknown Mintlify fields; the runtime ignores fields it does not consume.

**4. Run it**

```bash
npx vite
```

Your site should render at `http://localhost:5173`.

### What transfers directly

* **Navigation**: tabs, groups, pages, anchors, versions, dropdowns, products
* **MDX components**: Accordions, Cards, Callouts, Steps, Tabs, Code Groups, Expandables
* **Frontmatter**: `title`, `description`, `icon`, `sidebarTitle`, `tag`, `hidden`, `deprecated`
* **Config**: `colors`, `logo`, `favicon`, `navbar`, `footer`, `redirects`, `appearance`, `fonts`, `banner`
* **OpenAPI tabs**: `{ "tab": "API Reference", "openapi": "openapi.json" }` generates pages from your spec

### What is different

| Area           | Mintlify               | Holocron                                    |
| -------------- | ---------------------- | ------------------------------------------- |
| Hosting        | Mintlify cloud         | Self-hosted (Node.js or Cloudflare Workers) |
| Build          | Cloud build on push    | Local `vite build`                          |
| API playground | Interactive playground | Read-only API reference                     |
| Analytics      | Built-in dashboard     | Bring your own                              |
| Custom domains | Dashboard setting      | Your hosting provider                       |
| Search         | Algolia/built-in       | Orama (local, built-in)                     |

## How it works

```diagram
  docs.json + MDX files
          │
          ▼
  ┌────────────────┐
  │  Vite Plugin   │  reads config, syncs navigation tree, processes MDX
  │  (holocron())  │
  └───────┬────────┘
          │
          ▼
  ┌────────────────┐
  │   Spiceflow    │  React Server Components framework
  │   + Tailwind   │  auto-added by the plugin
  └───────┬────────┘
          │
          ▼
  Full docs site with search, OpenAPI, dark mode, AI exports
```

The plugin reads your config file, walks the navigation tree to discover MDX pages, and generates virtual modules that the Spiceflow app consumes at render time. Only changed files get re-parsed on subsequent builds thanks to a git-SHA-based cache.

## Deploy

### Holocron hosting

The fastest way to get a live URL. Builds and uploads your site to `holocron.so`:

```bash
npx -y "@holocron.so/cli" deploy
```

In **GitHub Actions**, the deploy command uses OIDC tokens automatically (no API key needed). Add `permissions: id-token: write` to your workflow.

### Node.js

```bash
npx vite build
node dist/rsc/index.js
```

The build output is a standard Node.js server. Deploy it to any platform that runs Node.

### Cloudflare Workers

```bash
npx vite build
npx wrangler deploy
```

See [Cloudflare deploy docs](/docs/deploy/cloudflare) for `wrangler.jsonc` setup.

## AI-readable docs

Every Holocron site generates AI-friendly endpoints out of the box:

* **`.md` routes**: append `.md` to any page URL to get raw markdown. `https://your-site.com/quickstart.md`
* **`/llms.txt`**: an index of all pages with titles and `.md` URLs. Agents read this to discover the site structure.
* **`/docs.zip`**: download every page as a `.md` file in one zip. Agents can grep it locally.
* **Skill discovery**: `/.well-known/agent-skills/index.json` exposes your docs as an installable AI skill.

```bash
curl https://your-site.com/llms.txt
curl -L https://your-site.com/docs.zip -o docs.zip
```

## Project structure

```diagram
my-docs/
├── index.mdx           # pages are MDX files
├── guides/
│   ├── install.mdx
│   └── deploy.mdx
├── docs.json           # navigation and config
├── vite.config.ts      # one-line plugin setup
├── package.json
└── public/             # static assets (logos, images)
```

## Explore

Full documentation at **[holocron.so](https://holocron.so)**.

* [Quickstart](/docs/quickstart)
* [Navigation](/docs/organize/navigation): tabs, groups, pages, anchors, versions, dropdowns
* [Theme and Colors](/docs/customize/theme): shadcn-compatible CSS variables
* [OpenAPI Reference](/docs/api-docs/openapi): generate API docs from a spec
* [MDX Components](/docs/components): Accordions, Cards, Callouts, Steps, Tabs, and more
* [Deploy](/docs/deploy/node): Node.js, Cloudflare Workers, and holocron.so hosting

## License

[MIT](https://github.com/remorses/holocron/blob/main/LICENSE). If you use Holocron for your docs, please keep the "Powered by Holocron" footer link. It helps others discover the project.

## Explore

<CardGroup cols={2}>
  <Card title="Quickstart" icon="zap" href="/docs/quickstart">
    Get a docs site running in under a minute.
  </Card>

  <Card title="Migration from Mintlify" icon="arrow-right-left" href="/docs/migration">
    Move an existing Mintlify project to Holocron.
  </Card>

  <Card title="Mintlify open source alternative" icon="scale" href="/docs/mintlify-open-source-alternative">
    Drop-in Mintlify replacement. Same docs.json and MDX, MIT licensed.
  </Card>

  <Card title="Navigation" icon="panel-left" href="/docs/organize/navigation">
    Tabs, groups, pages, anchors, versions, and dropdowns.
  </Card>

  <Card title="Theme and Colors" icon="palette" href="/docs/customize/theme">
    shadcn-compatible CSS variables and color tokens.
  </Card>

  <Card title="OpenAPI Reference" icon="braces" href="/docs/api-docs/openapi">
    Generate API docs from an OpenAPI spec.
  </Card>

  <Card title="AI-Readable Docs" icon="bot" href="/docs/ai/llms-txt">
    /llms.txt, /docs.zip, .md routes, and skill discovery.
  </Card>

  <Card title="MDX Components" icon="blocks" href="/docs/components">
    Accordions, Cards, Callouts, Steps, Tabs, and more.
  </Card>

  <Card title="Deploy" icon="cloud-upload" href="/docs/deploy/node">
    Node.js, Cloudflare Workers, and build output.
  </Card>
</CardGroup>


---
title: What is Holocron
url: "https://holocron.so/docs/what-is-holocron.md"
description: "Open source Mintlify replacement that runs as a Vite plugin. Local builds, standard Git workflows, and deploy anywhere."
---

# What is Holocron

Holocron is a **documentation site generator** built as a Vite plugin. You write MDX files, define your navigation in a `docs.json` config, and Holocron produces a full docs site with search, dark mode, OpenAPI reference, and AI-readable exports.

It is designed as a **Mintlify-compatible replacement**. If you already have a Mintlify project, you can point Holocron at the same `docs.json` and MDX files. Holocron accepts unknown Mintlify fields and supports the common component vocabulary it renders today. For the full comparison, see [Mintlify open source alternative](/docs/mintlify-open-source-alternative).

## Why Holocron

* **Local builds.** Run `vite build` and deploy the output anywhere. No hosted service required.
* **Normal Git workflows.** Your docs live in your repo. PRs, branches, diffs all work the way you expect.
* **Vite-native.** HMR, fast rebuilds, Tailwind, React Server Components, all powered by Spiceflow under the hood.
* **Mintlify compatibility.** Familiar `docs.json`, MDX components (`Tabs`, `Cards`, `Callouts`, `Steps`, etc.), and frontmatter fields.
* **AI-first.** Every page gets a `.md` route, plus `/llms.txt`, `/docs.zip`, and `.well-known/agent-skills/` for agent discovery.

## How it works

```diagram
docs.json + MDX files
        │
        v
  ┌──────────────┐
  │ Vite Plugin  │   reads config, syncs navigation tree, processes MDX
  │ (holocron()) │
  └──────┬───────┘
         │
         v
  ┌──────────────┐
  │  Spiceflow   │   React Server Components framework
  │  + Tailwind  │   auto-added by the plugin
  └──────┬───────┘
         │
         v
  Full docs site with search, OpenAPI, dark mode, AI exports
```

The plugin reads your config file, walks the navigation tree to discover MDX pages, and generates virtual modules that the Spiceflow app consumes at render time. Local images outside `public/` can be copied into Holocron's generated image directory with dimensions and placeholders; images already in `public/` keep their public URL. Only changed files get re-parsed on subsequent builds thanks to a git-SHA-based cache.

## What you get

| Feature    | Details                                                             |
| ---------- | ------------------------------------------------------------------- |
| Navigation | Tabs, groups, nested groups, anchors, versions, dropdowns, products |
| Components | Accordions, Cards, Callouts, Steps, Tabs, Code Groups, and more     |
| OpenAPI    | Point at a spec file, get auto-generated API reference pages        |
| Search     | Built-in Orama search across navigation and headings                |
| Theming    | shadcn-compatible CSS variables, custom fonts, logo, favicon        |
| AI exports | `/llms.txt`, `/docs.zip`, `.md` per page, skill discovery           |
| Deployment | Node.js or Cloudflare Workers                                       |


---
title: Quickstart
url: "https://holocron.so/docs/quickstart.md"
description: "Install the Vite plugin, create a docs.json config, write your first MDX page, and start the dev server in under a minute."
---

# Quickstart

## Install

```bash
pnpm add @holocron.so/vite react react-dom vite
```

## Create vite.config.ts

```ts
import { defineConfig } from 'vite'
import { holocron } from '@holocron.so/vite'

export default defineConfig({
  plugins: [holocron()],
})
```

The plugin auto-adds Spiceflow, Tailwind CSS, and React. You do not need to install or configure them separately.

## Create docs.json

Create a `docs.json` file at the project root:

```json
{
  "name": "My Docs",
  "colors": { "primary": "#6366f1" },
  "navigation": [
    {
      "group": "Getting Started",
      "pages": ["index"]
    }
  ]
}
```

## Write your first page

Create `index.mdx`:

```mdx
---
$schema: https://holocron.so/frontmatter.json
title: Welcome
description: My documentation site.
---

# Welcome

This is my first Holocron page.
```

## Run the dev server

```bash
npx vite
```

Open `http://localhost:5173` and you should see your docs site.

## Build for production

```bash
npx vite build
```

Start the production server:

```bash
node dist/rsc/index.js
```

## Project structure

A minimal Holocron project looks like this:

```diagram
my-docs/
├── index.mdx
├── docs.json
├── vite.config.ts
├── package.json
└── public/           # static assets (logos, images)
```

By default, Holocron looks for MDX files relative to the project root. You can change this with the `pagesDir` option. See [Pages directory](/docs/organize/pages-dir) for details.


---
title: Migration from Mintlify
url: "https://holocron.so/docs/migration.md"
description: "Move an existing Mintlify project to Holocron. Keep your docs.json, MDX components, and frontmatter fields as-is."
---

# Migration from Mintlify

If you already have a Mintlify docs project, migrating to Holocron takes a few minutes. Holocron reads a Mintlify-compatible `docs.json` shape and supports the common MDX component vocabulary it renders today. Holocron is the [Mintlify open source alternative](/docs/mintlify-open-source-alternative): same files, local Vite builds, MIT license.

## Step 1: Install dependencies

In your existing docs directory:

```bash
pnpm add @holocron.so/vite react react-dom vite
```

## Step 2: Create vite.config.ts

```ts
import { defineConfig } from 'vite'
import { holocron } from '@holocron.so/vite'

export default defineConfig({
  plugins: [holocron()],
})
```

## Step 3: Keep your docs.json

Your existing `docs.json` usually works as-is. Holocron's schema accepts unknown Mintlify fields and silently ignores anything it does not consume, like Mintlify-specific API playground settings. Analytics integrations (`integrations` field) work out of the box.

## Step 4: Run it

```bash
npx vite
```

Your site should render at `http://localhost:5173`.

## What transfers directly

* **Navigation structure**: tabs, groups, pages, anchors, versions, dropdowns, products
* **MDX components**: Accordions, Cards, Callouts, Steps, Tabs, Code Groups, Expandables, and more
* **Frontmatter fields**: `title`, `description`, `icon`, `sidebarTitle`, `tag`, `hidden`, `deprecated`
* **Config fields**: `colors`, `logo`, `favicon`, `navbar`, `footer`, `redirects`, `appearance`, `fonts`, `banner`, `integrations`
* **OpenAPI tabs**: `{ "tab": "API Reference", "openapi": "openapi.json" }` generates pages from your spec

## What is different

| Area           | Mintlify               | Holocron                                                  |
| -------------- | ---------------------- | --------------------------------------------------------- |
| Hosting        | Mintlify cloud         | Self-hosted (Node.js or Cloudflare Workers)               |
| Build          | Cloud build on push    | Local `vite build`                                        |
| API playground | Interactive playground | Read-only API reference                                   |
| Analytics      | Built-in dashboard     | Same `integrations` field (GA4, PostHog, Plausible, etc.) |
| Custom domains | Dashboard setting      | Your hosting provider                                     |
| Search         | Algolia/built-in       | Orama (local, built-in)                                   |

## Component compatibility

See the [MDX Components](/docs/components) tab for a full list of supported components and any behavioral differences. Most components render identically. A few Mintlify-specific components (like the API playground) are not supported.

## Tips

* If your pages live in a subdirectory, use `holocron({ pagesDir: './pages' })` in `vite.config.ts`.
* Holocron supports `docs.json`, `docs.jsonc`, and `holocron.jsonc`. First found wins.
* The `$schema` field in your config is ignored at runtime but useful for editor autocomplete.


---
title: Mintlify open source alternative
url: "https://holocron.so/docs/mintlify-open-source-alternative.md"
description: "Holocron is the open source Mintlify alternative. Keep your docs.json and MDX files, then build locally and deploy anywhere."
---

# Mintlify open source alternative

**Holocron** is the **open source Mintlify alternative**. It is a **drop-in replacement**: keep your `docs.json`, MDX pages, OpenAPI spec, and Mintlify components, then run them as a **Vite plugin**. Builds happen locally. You can self-host, or publish with the Holocron CLI.

Mintlify is a hosted docs platform. Beautiful defaults, Git sync, an AI assistant, and an API playground. It is also closed source, cloud-only, and **Pro starts at $450 per month**. Teams searching for a **Mintlify open source alternative** usually want the same authoring model without the lock-in.

<Aside>
  <Tip>
    Already on Mintlify? Keep the repo as-is. Add a Vite config and run it. See [Migration from Mintlify](/docs/migration).
  </Tip>
</Aside>

```bash
npx -y "@holocron.so/cli" create
```

## Why this query exists

People type **mintlify open source alternative** when they want three things at once:

1. **Mintlify-compatible authoring.** `docs.json`, MDX, Cards, Callouts, Steps, OpenAPI tabs.
2. **Open source.** Readable code, no vendor roadmap risk, MIT license.
3. **Self-hosting.** Local `vite build`, standard CI, deploy on Node.js or Cloudflare Workers.

Most lists that rank for this query point at **Docusaurus**, **Fumadocs**, **Scalar**, or GitBook. Those are good tools. None of them render a Mintlify repo without a rewrite. Holocron does.

```diagram
  your Mintlify repo                                      Holocron site
  ┌────────────────────────────────┐                      ┌────────────────────────────────┐
  │ docs.json                      │                      │ same docs.json                 │
  │ *.mdx  (Cards, Callouts, Steps)│── add plugin ───────>│ vite.config.ts                 │
  │ openapi.yaml                   │                      │ vite build / holocron deploy   │
  └────────────────────────────────┘                      └────────────────────────────────┘
         no content rewrite                                      local preview in seconds
```

<CardGroup cols={2}>
  <Card title="What is Holocron" icon="info" href="/docs/what-is-holocron">
    Vite plugin, local builds, Mintlify-compatible config and components.
  </Card>

  <Card title="Migrate from Mintlify" icon="arrow-right-left" href="/docs/migration">
    Four steps. Keep docs.json and MDX. Run npx vite.
  </Card>

  <Card title="Quickstart" icon="zap" href="/docs/quickstart">
    Scaffold a site, write one MDX page, start the dev server.
  </Card>

  <Card title="Pricing" icon="credit-card" href="/docs/pricing">
    Generator is MIT and free. Optional Pro is $99 per site per month.
  </Card>
</CardGroup>

## Drop-in Mintlify replacement

Holocron follows the **Mintlify `docs.json` shape**. Unknown Mintlify fields pass through validation, so you can paste a full Mintlify config without errors. Fields Holocron does not consume are ignored.

**What transfers as-is:**

* **Navigation.** Tabs, groups, nested groups, anchors, versions, dropdowns, products.
* **MDX components.** Accordions, Cards, Callouts, Steps, Tabs, Code Groups, Expandables, Frames, Fields, and more. See [Components](/docs/components/index).
* **Frontmatter.** `title`, `description`, `icon`, `sidebarTitle`, `tag`, `hidden`, `deprecated`.
* **Site chrome.** Colors, logo, favicon, navbar, footer, redirects, appearance, fonts, banner, integrations.
* **OpenAPI tabs.** `{ "tab": "API Reference", "openapi": "openapi.json" }` generates one page per endpoint.

```ts
import { defineConfig } from 'vite'
import { holocron } from '@holocron.so/vite'

export default defineConfig({
  plugins: [holocron()],
})
```

Point that plugin at an existing Mintlify project. The same files that used to publish only through Mintlify cloud now preview on `localhost` with Vite HMR.

<Aside>
  <Note>
    Holocron accepts `docs.json`, `docs.jsonc`, and `holocron.jsonc`. First found wins. Put `"$schema": "https://holocron.so/docs.json"` in the file for editor autocomplete.
  </Note>
</Aside>

## Holocron vs Mintlify

| Area                 | Mintlify                                       | Holocron                                                                                |
| -------------------- | ---------------------------------------------- | --------------------------------------------------------------------------------------- |
| **License**          | Closed source SaaS                             | **MIT**, [github.com/remorses/holocron](https://github.com/remorses/holocron)           |
| **Hosting**          | Mintlify cloud only                            | Self-host, or [holocron.so hosting](/docs/deploy/holocron)                              |
| **Build**            | Cloud build on git push                        | Local **`vite build`**, standard CI                                                     |
| **Config**           | `docs.json`                                    | Same `docs.json`                                                                        |
| **Components**       | Proprietary MDX set                            | Same names, open source implementations                                                 |
| **Search**           | Hosted search                                  | **Orama**, local, zero config                                                           |
| **API reference**    | Interactive playground                         | Generated OpenAPI pages, request and response examples                                  |
| **AI assistant**     | Hosted assistant (Pro)                         | Built-in chat on the docs site, plus an [embeddable widget](/docs/ai/chat-widget)       |
| **Docs maintenance** | Writing agent and automations (Pro)            | [Holocron Maintain](/maintain/index) from page generation prompts                       |
| **AI exports**       | `.md` routes, `llms.txt`, MCP, `skill.md`      | `.md` routes, `/llms.txt`, **`/docs.zip`**, [skill discovery](/docs/ai/skill-discovery) |
| **Custom app**       | Not a framework                                | Mount docs inside a [Spiceflow](/docs/spiceflow) app                                    |
| **Pricing**          | Free hobby, **Pro $450/mo**, Enterprise custom | Generator **free**. Optional Pro **$99/mo** per site                                    |

Holocron is not a pixel clone of every Mintlify dashboard product. It is the **open source docs engine** that understands Mintlify content.

## Feature set

This is what the codebase ships today, mapped against the Mintlify feature surface people actually use.

### Docs as code

Write **MDX** pages. Organize them with [docs.json](/docs/organize/docs-json). Preview with Vite. Commit with Git. That is the same workflow Mintlify popularized, minus the hosted compiler.

* [Pages and frontmatter](/docs/create/pages)
* [MDX syntax](/docs/create/mdx)
* [Code blocks](/docs/create/code)
* [Images](/docs/create/images)
* [Local TSX and Markdown imports](/docs/create/local-imports)
* [ASCII diagrams](/docs/create/diagrams)
* [Redirects](/docs/create/redirects)
* [Broken link detection](/docs/create/broken-links)

<Aside>
  <Info>
    Mintlify's **web editor** is a hosted CMS on top of Git. Holocron stays **docs-as-code**. Edit in the repo, in PRs, or with your own editor.
  </Info>
</Aside>

### Navigation that matches Mintlify

[Navigation](/docs/organize/navigation) is the same tree: tabs, groups, pages. Holocron also implements:

* **[Versions](/docs/organize/versions)** for API or product lines
* **[Dropdowns](/docs/organize/dropdowns)** and products, flattened into routes
* **[Hidden pages](/docs/organize/hidden-pages)** that stay out of the sidebar
* **[Imageboard](/docs/organize/imageboard)** for screenshot-heavy catalogs
* **[pagesDir](/docs/organize/pages-dir)** when MDX does not live at the repo root

### Theming and layout

Override **shadcn-style CSS variables** and Holocron adapts. No proprietary theme JSON required.

* [Theme and colors](/docs/customize/theme)
* [Fonts](/docs/customize/fonts)
* [Icons](/docs/customize/icons) (Lucide, Font Awesome, Tabler)
* [Logo and favicon](/docs/customize/logo-and-favicon)
* [Navbar, footer, banner](/docs/customize/navbar-footer-banner)
* [Custom CSS](/docs/customize/custom-css)
* [Page modes and layout](/docs/customize/layout)
* [Bleed](/docs/customize/bleed) for wide media

### Mintlify-compatible MDX components

In MDX, components are **global**. No imports. The names match Mintlify so existing pages keep compiling.

<CardGroup cols={3}>
  <Card title="Callouts" icon="badge-alert" href="/docs/components/callouts">
    Note, Warning, Info, Tip, Check, Danger.
  </Card>

  <Card title="Cards" icon="layout-grid" href="/docs/components/cards">
    Card and CardGroup grids.
  </Card>

  <Card title="Steps" icon="list-ordered" href="/docs/components/steps">
    Numbered procedures.
  </Card>

  <Card title="Tabs" icon="columns-3" href="/docs/components/tabs">
    Switchable content panes.
  </Card>

  <Card title="Accordions" icon="chevrons-up-down" href="/docs/components/accordions">
    Collapsible sections.
  </Card>

  <Card title="API fields" icon="braces" href="/docs/components/fields">
    Param, ResponseField, Expandable.
  </Card>
</CardGroup>

Also: [Code groups](/docs/components/code-groups), [Frames](/docs/components/frames), [Mermaid](/docs/components/mermaid-diagrams), [Tree](/docs/components/tree), [Update](/docs/components/update), [Prompt](/docs/components/prompt), [Visibility](/docs/components/visibility), [Panel](/docs/components/panel), [Tiles](/docs/components/tiles). Full index: [Components](/docs/components/index).

### OpenAPI reference

Point a tab at an OpenAPI file. Holocron generates **one MDX page per endpoint**, with parameters, examples, and SDK snippets.

Mintlify's **interactive API playground** (live "Try it" against a server) is a hosted product. Holocron renders a **read-only reference** from the spec. For most public API docs that is the page users read.

* [OpenAPI setup](/docs/api-docs/openapi)
* [Generated pages](/docs/api-docs/generated-pages)
* [Request and response examples](/docs/api-docs/request-response-examples)
* [SDK examples](/docs/api-docs/sdk-examples)

### Changelog and MCP docs

Mintlify can turn GitHub releases and MCP servers into docs surfaces. So can Holocron:

* **[Changelog tab](/docs/changelog-tab)** from a GitHub repo URL, one `<Update>` per release
* **[MCP docs](/docs/mcp-tools)** from a local JSON file or a remote MCP server
* **[MCP export](/docs/mcp-export)** so agents can install your docs as tools

### AI-readable docs

Mintlify markets agent-native docs: Markdown for crawlers, `llms.txt`, MCP, `skill.md`. Holocron ships the same idea, then adds bulk download.

| Endpoint                         | What agents get                                                                       |
| -------------------------------- | ------------------------------------------------------------------------------------- |
| **`page.md`**                    | Raw Markdown for any page. See [Markdown routes](/docs/ai/markdown-routes).           |
| **`/llms.txt`**                  | Index of titles and `.md` URLs. See [llms.txt](/docs/ai/llms-txt).                    |
| **`/docs.zip`**                  | Every page as `.md` in one archive. See [docs.zip](/docs/ai/docs-zip).                |
| **`/.well-known/agent-skills/`** | Installable skill for coding agents. See [Skill discovery](/docs/ai/skill-discovery). |

<Aside>
  <Tip>
    `/docs.zip` is the fastest way for an agent to grep a whole site. Mintlify does not ship this archive.
  </Tip>
</Aside>

### AI assistant and chat widget

Enable [AI Assistant](/docs/ai/assistant) in `docs.json`. Readers ask questions against your docs. Display it in the **sidebar** or as a **floating** pill.

The same chat can be [embedded on any website](/docs/ai/chat-widget), not only the docs origin.

Free hosted sites get a trial model. **Holocron Pro** ($99 per site per month) unlocks the full model and 50,000 credits. Compare that with Mintlify Pro at **$450 per month** plus credit overages.

### Holocron Maintain

Mintlify Pro includes a **writing agent** and scheduled automations. Holocron's counterpart is **[Maintain](/maintain/index)**.

Every page can store a **generation `prompt`** in frontmatter. When referenced source files change, Maintain reruns that prompt and updates the MDX. Run it from a parent agent, or from [GitHub Actions](/maintain/github-actions) so a pull request opens on push.

### Deploy anywhere

Mintlify publishes only to Mintlify cloud. A **Mintlify open source alternative** has to leave with you.

```diagram
                              vite build
                                  │
                                  v
                 ┌────────────────┼────────────────┐
                 v                v                v
            Node server      Cloudflare       holocron deploy
            dist/rsc         Workers          managed hosting
```

* [Node.js](/docs/deploy/node): `npx vite build && node dist/rsc/index.js`
* [Cloudflare Workers](/docs/deploy/cloudflare): `npx wrangler deploy`
* [Holocron hosting](/docs/deploy/holocron): `npx -y "@holocron.so/cli" deploy`
* [Subpath hosting](/docs/deploy/base-path): docs at `yoursite.com/docs`
* [Multi-tenant](/docs/deploy/multi-tenant): one worker, many sites

<Aside>
  <Note>
    Custom domains and unlimited preview deployments are **Holocron Pro**. The open source generator itself has no seat limit and no cloud requirement.
  </Note>
</Aside>

### Custom routes, not a walled garden

Mintlify is a docs host. Holocron is a **Vite plugin** on [Spiceflow](/docs/spiceflow). [Custom entry](/docs/custom-entry) mounts docs next to your own API routes, auth, and pages. That is how holocron.so itself is built.

## What Holocron does not copy

Honesty ranks better than a fake parity table.

* **Interactive API playground.** Holocron generates reference pages. It does not proxy live authenticated requests from the browser.
* **Hosted web editor.** Edit Markdown in Git. There is no Mintlify-style browser CMS.
* **Mintlify analytics dashboard.** Use the `integrations` field (GA4, PostHog, Plausible, and similar). A native Holocron analytics view is not shipping yet.
* **SSO, SCIM, RBAC, PDF export.** Those are Mintlify Enterprise products. Self-host Holocron behind your own auth if you need a private site.
* **Mintlify Index** (cross-publisher search API) and **static export** as a paid REST job. Holocron sites already emit Markdown and a zip; you own the build output.

If those hosted extras are the product you want, stay on Mintlify. If you want **your Mintlify files, locally, as open source**, Holocron is the drop-in.

## Holocron vs other Mintlify alternatives

Search results for this query mix SaaS clones and generic static generators. The split is simple.

| Tool                      | Open source? | Renders a Mintlify repo as-is?                                    |
| ------------------------- | ------------ | ----------------------------------------------------------------- |
| **Holocron**              | Yes, MIT     | **Yes.** Same `docs.json` and MDX components.                     |
| Docusaurus                | Yes          | No. Different config, different components, rewrite required.     |
| Fumadocs                  | Yes          | No. Next.js framework with its own MDX map.                       |
| Scalar                    | Yes          | No. API-reference first, not a Mintlify docs.json runtime.        |
| GitBook                   | No           | No. Different editor and Git sync model.                          |
| Unmint and similar themes | Partial      | Visual mimic, not a drop-in compiler for existing Mintlify pages. |

The ranking intent behind **mintlify open source alternative** is not "any docs tool that is free." It is "Mintlify, without the closed cloud." Holocron is built for that intent.

## Migrate in four steps

From [Migration from Mintlify](/docs/migration):

1. `pnpm add @holocron.so/vite react react-dom vite`
2. Add the `holocron()` plugin in `vite.config.ts`
3. Keep `docs.json` and every `.mdx` file
4. `npx vite`

Open `http://localhost:5173`. If pages live in a subfolder, set `holocron({ pagesDir: './pages' })`.

## FAQ

### Is there an open source Mintlify alternative?

**Yes. Holocron.** It is MIT licensed, runs as a Vite plugin, and reads Mintlify `docs.json` plus MDX. Source: [github.com/remorses/holocron](https://github.com/remorses/holocron).

### Can I self-host Mintlify?

**Not the Mintlify platform.** Mintlify is cloud-only on paid plans; even Enterprise self-hosting is a sales conversation. Holocron self-hosts on **Node.js** or **Cloudflare Workers** with a normal `vite build`.

### Does Holocron support Mintlify components?

**Yes**, for the components it documents: Accordions, Cards, Callouts, Steps, Tabs, Code Groups, Expandables, Fields, Frames, and the rest of the [component catalog](/docs/components/index). Prop interfaces stay Mintlify-compatible. Holocron-only extras are additive.

### How much does a Mintlify open source alternative cost?

The Holocron **generator is free**. Optional **Holocron Pro** is **$99 per site per month** ($990 per year) for the full AI model, Maintain, custom domains, and unlimited preview deployments. Mintlify **Pro is $450 per month**.

### Is Docusaurus a Mintlify alternative?

Docusaurus is an open source docs generator. It is **not drop-in**. You rewrite navigation, components, and often the page layout. Use Docusaurus if you already live in that ecosystem. Use Holocron if you already have Mintlify files.

## Get started

<CardGroup cols={2}>
  <Card title="Install Holocron" icon="rocket" href="/docs/quickstart">
    Create vite.config.ts, add docs.json, write index.mdx.
  </Card>

  <Card title="Switch from Mintlify" icon="arrow-right-left" href="/docs/migration">
    Keep the repo. Add the plugin. Preview locally.
  </Card>
</CardGroup>


---
title: Pages
url: "https://holocron.so/docs/create/pages.md"
description: "Frontmatter fields, page slugs, sidebar titles, icons, tags, and metadata options for MDX documentation pages."
---

# Pages

Every page in a Holocron site is an **MDX file** (.mdx or .md). Pages are discovered through the navigation config in `docs.json`, not by convention.

## Page slugs

The slug of a page is its file path relative to the pages directory, without the extension:

| File path             | Slug              |
| --------------------- | ----------------- |
| `index.mdx`           | `index`           |
| `getting-started.mdx` | `getting-started` |
| `guides/auth.mdx`     | `guides/auth`     |
| `api/users.mdx`       | `api/users`       |

Reference these slugs in your `docs.json` navigation:

```json
{
  "navigation": [
    {
      "group": "Guides",
      "pages": ["getting-started", "guides/auth"]
    }
  ]
}
```

## Frontmatter

Every page can have YAML frontmatter at the top. Add `$schema` for editor
autocompletion and validation:

```mdx
---
$schema: https://holocron.so/frontmatter.json
title: Authentication
description: How to set up auth in your app.
icon: lucide:lock
sidebarTitle: Auth
tag: New
prompt: |
  Write the authentication guide from @/src/auth/.
---
```

### Supported fields

| Field                 | Type      | Description                                                                                                                                                                                                                    |
| --------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `$schema`             | string    | JSON Schema URL. Use `https://holocron.so/frontmatter.json` for editor validation                                                                                                                                              |
| `title`               | string    | Page title, used in sidebar and `<title>` tag                                                                                                                                                                                  |
| `prompt`              | string    | Original generation recipe for [Holocron Maintain](/maintain). Use `@/path` and `@https://` references                                                                                                                         |
| `description`         | string    | SEO description and subtitle                                                                                                                                                                                                   |
| `icon`                | string    | Sidebar icon. Library name, prefixed name (`lucide:lock`), emoji, remote URL, or root-absolute SVG path (`/icons/vercel.svg`)                                                                                                  |
| `iconColor`           | string    | Color for the sidebar icon. Named colors (`green`, `blue`, `red`, `purple`, `orange`, `yellow`, `pink`) or any CSS color string                                                                                                |
| `sidebarTitle`        | string    | Short label for the sidebar, letting `title` be longer for SEO                                                                                                                                                                 |
| `sidebarToc`          | boolean   | Show or hide the page's section headings under its sidebar entry. Defaults to shown, unless the page renders a [`TableOfContentsPanel`](/docs/components/table-of-contents)                                                    |
| `tag`                 | string    | Badge label next to the page title in sidebar                                                                                                                                                                                  |
| `mode`                | string    | Page layout mode: `"default"`, `"compact"`, `"center"`, or `"custom"`. Compact hides the sidebar assistant. Site-level compact in `docs.json` defaults `assistant.display` to `floating`. See [Layout](/docs/customize/layout) |
| `maxWidth`            | number    | Maximum content width in pixels. Useful with `mode: "custom"`                                                                                                                                                                  |
| `deprecated`          | boolean   | Marks the page as deprecated                                                                                                                                                                                                   |
| `api`                 | string    | Mintlify API page label like `GET /users`                                                                                                                                                                                      |
| `hidden`              | boolean   | Hides the page from navigation and adds `noindex`                                                                                                                                                                              |
| `noindex`             | boolean   | Keeps the page visible but adds robots `noindex`                                                                                                                                                                               |
| `cache-control`       | string    | Custom response cache header                                                                                                                                                                                                   |
| `keywords`            | string\[] | Additional keywords for search                                                                                                                                                                                                 |
| `robots`              | string    | Custom robots meta value                                                                                                                                                                                                       |
| `og:title`            | string    | Open Graph title override                                                                                                                                                                                                      |
| `og:description`      | string    | Open Graph description                                                                                                                                                                                                         |
| `og:image`            | string    | Open Graph image URL                                                                                                                                                                                                           |
| `twitter:title`       | string    | Twitter card title                                                                                                                                                                                                             |
| `twitter:description` | string    | Twitter card description                                                                                                                                                                                                       |
| `twitter:image`       | string    | Twitter card image                                                                                                                                                                                                             |

## Open Graph image

By default, Holocron **auto-generates an OG image** for every page using the page title, description, and site favicon. This works out of the box with no configuration needed.

To use a **custom image** instead, set `og:image` in the page frontmatter. Both absolute URLs and relative paths work; relative paths are resolved to absolute URLs automatically.

```mdx
---
$schema: https://holocron.so/frontmatter.json
title: Getting Started
"og:image": /images/getting-started-og.png
"og:image:width": 1200
"og:image:height": 630
---
```

Place the image file in your `public/` folder (e.g. `public/images/getting-started-og.png`). The recommended size is **1200x630** pixels.

You can also override the Twitter card image separately with `twitter:image`. If not set, it falls back to `og:image`.

## Sidebar title

Use `sidebarTitle` to show a **shorter label** in the sidebar while keeping a longer, more descriptive `title` for SEO and browser tabs.

The `title` field controls the `<title>` tag, which is what Google shows in search results. Longer, keyword-rich titles rank better. But long titles make the sidebar hard to scan, so `sidebarTitle` lets you decouple the two.

```mdx
---
$schema: https://holocron.so/frontmatter.json
title: Authentication — Setting Up OAuth and API Keys
sidebarTitle: Authentication
---
```

In this example, Google sees "Authentication — Setting Up OAuth and API Keys" but the sidebar just shows "Authentication".

## Local SVG page icon

Library names, prefixed names, and emoji all work in `icon`. For a **brand mark**, put an SVG in `public/` and point `icon` at the **root-absolute** path:

```mdx
---
$schema: https://holocron.so/frontmatter.json
title: Vercel deployments
sidebarTitle: Vercel
icon: /icons/vercel.svg
---
```

Place the file at `public/icons/vercel.svg`. Use **`currentColor`** for `fill` and `stroke` so the mark matches Lucide icons in the sidebar. See [Icons](/docs/customize/icons) for the full reference.

## The index page

The page with slug `index` renders at `/`. Every other slug maps directly to a URL path (e.g. `guides/auth` renders at `/guides/auth`).


---
title: MDX
url: "https://holocron.so/docs/create/mdx.md"
description: "Write documentation with MDX, a superset of Markdown with JSX support."
---

# MDX

Holocron pages are written in **MDX**, a format that combines Markdown with JSX components. You get the simplicity of Markdown for prose and the power of React components for interactive elements.

## Basic Markdown

Standard Markdown works as expected:

```mdx
# Heading 1
## Heading 2
### Heading 3

Regular paragraph text with **bold**, *italic*, and `inline code`.

- Bullet list
- Another item

1. Numbered list
2. Second item

> Blockquote text

[Link text](https://example.com)
```

## Using components

Holocron ships with built-in components that you can use directly in MDX without importing them:

```mdx
<Note>
This is a note callout. No import needed.
</Note>

<Steps>
  <Step title="First step">
    Do this first.
  </Step>
  <Step title="Second step">
    Then do this.
  </Step>
</Steps>
```

See the [MDX Components](/docs/components) tab for the full list of available components.

## Tables

Markdown tables render with Holocron's editorial styling:

```mdx
| Method | Path | Description |
|--------|------|-------------|
| GET | /users | List users |
| POST | /users | Create user |
```

## Headings and table of contents

Every heading (`##`, `###`, etc.) automatically appears in the right-side table of contents. The `#` heading is used as the page title.

## Content inside components

When putting MDX content inside container components, always use multi-line form:

```mdx
<!-- ✅ Correct -->
<Note>
Use `Note` for neutral information.
</Note>

<!-- ❌ Wrong — text won't get proper paragraph styling -->
<Note>Use `Note` for neutral information.</Note>
```

This is an MDX parser limitation. Multi-line form gets proper paragraph wrapping; single-line form produces bare inline text.

Native heading elements (`<h1>` through `<h6>`) are auto-fixed: Holocron unwraps the paragraph wrapper so both multi-line and single-line forms produce the same clean output. For other leaf-like elements (`<span>`, `<div>`, or custom components), use **single-line form** to avoid the wrapper:

```mdx
<MyBanner className='text-xl'>Short announcement text</MyBanner>
```


---
title: Code Blocks
url: "https://holocron.so/docs/create/code.md"
description: "Syntax highlighting, code groups, and inline code."
---

# Code Blocks

Holocron highlights fenced code on the **server**. First paint already has token colors, and in-site navigation keeps them. Token classes match the usual Prism theme names (`token keyword`, `token string`, and so on).

## Basic code block

```ts
const greeting = 'Hello, world!'
console.log(greeting)
```

Line numbers are shown by default. Format:

````mdx
```ts
const greeting = 'Hello, world!'
console.log(greeting)
```
````

## Meta options

Add options after the language identifier to customize code blocks. Options use `key=value` syntax. Bare words (without `=`) become the **title**.

### Title

The first bare word(s) after the language become the title, useful for showing filenames:

```ts vite.config.ts
export default defineConfig({
  plugins: [holocron()],
})
```

````mdx
```ts vite.config.ts
export default defineConfig({
  plugins: [holocron()],
})
```
````

You can also set the title explicitly with `title="..."` for titles with special characters:

````mdx
```ts title="src/vite.config.ts"
export default defineConfig({
  plugins: [holocron()],
})
```
````

### Line numbers

Line numbers are **on by default**. Disable them with `lines=false`:

```bash lines=false
npm install @holocron.so/vite
```

````mdx
```bash lines=false
npm install @holocron.so/vite
```
````

### Wrap

Long lines scroll horizontally by default. Add the bare `wrap` flag (Mintlify-compatible) to soft-wrap them instead — useful for prose-like content such as prompts:

```text Example prompt wrap
Use the Holocron skill. I have a docs site with a broken sidebar link and I want you to find the page, fix the slug in docs.json, and verify the navigation renders correctly.
```

````mdx
```text Example prompt wrap
Use the Holocron skill. I have a docs site with a broken sidebar link and ...
```
````

Wrapped blocks always hide line numbers and ignore `highlight`: a wrapped logical line can span several visual rows, so a per-line number gutter or highlight overlay would misalign.

### Bleed

Fenced code blocks bleed into the **right** margin by default so the code text lines up with the prose left edge. Set `bleed=true` (or `bleed=both`) to extend into both margins, or `bleed=none` (or `bleed=false`) to keep the block fully inside the content column:

```ts bleed=true
import { defineConfig } from 'vite'
import { holocron } from '@holocron.so/vite'

export default defineConfig({
  plugins: [holocron()],
})
```

````mdx
```ts bleed=true
import { defineConfig } from 'vite'
...
```
````

The `bleed` meta accepts `true`/`both`, `right` (the fenced-block default), or `false`/`none`.

### Highlight lines

Dim all lines except the ones you want to focus on. Pass a comma-separated list of line numbers or ranges to `highlight`:

```ts highlight="1,4-5"
import { defineConfig } from 'vite'
import { holocron } from '@holocron.so/vite'

export default defineConfig({
  plugins: [holocron()],
})
```

````mdx
```ts highlight="1,4-5"
import { defineConfig } from 'vite'
import { holocron } from '@holocron.so/vite'

export default defineConfig({
  plugins: [holocron()],
})
```
````

### Combining options

Options can be combined freely:

```ts vite.config.ts highlight="3" bleed=true
import { defineConfig } from 'vite'
import { holocron } from '@holocron.so/vite'

export default defineConfig({
  plugins: [holocron()],
})
```

````mdx
```ts vite.config.ts highlight="3" bleed=true
import { defineConfig } from 'vite'
...
```
````

## Code groups

Group related code blocks into tabs with the `CodeGroup` component. The bare word after the language becomes the tab label:

<Tabs items={["npm", "pnpm", "yarn"]}>
  <Tab title="npm">
    ```bash
    npm install @holocron.so/vite
    ```
  </Tab>

  <Tab title="pnpm">
    ```bash
    pnpm add @holocron.so/vite
    ```
  </Tab>

  <Tab title="yarn">
    ```bash
    yarn add @holocron.so/vite
    ```
  </Tab>
</Tabs>

````mdx
<CodeGroup>

```bash npm
npm install @holocron.so/vite
```

```bash pnpm
pnpm add @holocron.so/vite
```

```bash yarn
yarn add @holocron.so/vite
```

</CodeGroup>
````

See [Code Groups](/docs/components/code-groups) for more options.

## Inline code

Use backticks for inline code: `` `variable` `` renders as `variable`.

## Rendering the CodeBlock component directly

If you want the exact same code block UI that MDX renders (copy button, line numbers, line highlighting), import the `CodeBlock` component from `@holocron.so/vite/mdx`. This is handy for rendering code outside of docs, for example in a **dashboard** or settings page.

```tsx Dashboard.tsx
import { CodeBlock } from '@holocron.so/vite/mdx'

const CLI_EXAMPLES = `npx "@holocron.so/cli" build
npx "@holocron.so/cli" dev`

export function CliHelp() {
  return (
    <CodeBlock lang='bash' showLineNumbers={false}>
      {CLI_EXAMPLES}
    </CodeBlock>
  )
}
```

MDX fences highlight on the server. Used on its own, `CodeBlock` shows the raw `children` string.

The `bleed` prop controls how far the block extends past its content column. The component **defaults to no bleed**, so it stays fully inside its parent, which is what you want in a dashboard, modal, or card. Opt into bleed only when you render inside the docs prose column:

| `bleed` value      | Behavior                                          |
| ------------------ | ------------------------------------------------- |
| `'both'` / `true`  | extends into both left and right margins          |
| `'right'`          | extends into the right margin only                |
| `'none'` / `false` | no bleed, stays fully inside its parent (default) |

| Prop              | Type      | Description                                            |
| ----------------- | --------- | ------------------------------------------------------ |
| `children`        | `string`  | The raw code to render and copy.                       |
| `lang`            | `string`  | Language id, defaults to `jsx`.                        |
| `showLineNumbers` | `boolean` | Line numbers on the left, on by default.               |
| `title`           | `string`  | Filename or label shown above the block.               |
| `highlight`       | `string`  | Comma-separated lines/ranges to focus, e.g. `"1-3,7"`. |
| `bleed`           | `boolean` | Extend into the page margins, off by default.          |


---
title: Images
url: "https://holocron.so/docs/create/images.md"
description: Local and remote images with automatic sizing and placeholders.
---

# Images

Holocron processes images at build time to prevent layout shifts and generate placeholders.

## Markdown images

Use standard Markdown image syntax:

```mdx
![Alt text](/images/screenshot.png)
```

Place image files in your `public/` directory and reference them with absolute paths. Public images keep their original URL.

## HTML img tags

You can also use `<img>` tags:

```mdx
<img src="/images/diagram.png" alt="Architecture diagram" />
```

Holocron's remark plugin converts `<img>` tags to its internal image component with dimensions when it can read the image metadata.

## Build-time processing

For local images outside `public/`, Holocron:

1. **Reads dimensions** from the file so the rendered `<img>` has explicit `width` and `height`, preventing layout shift.
2. **Generates placeholders** for a smooth loading experience.
3. **Copies images** to a generated Holocron image directory during build for consistent serving.

Images already in `public/` are served from their public path and are not copied again.

## Remote images

Remote image URLs (`https://...`) are supported. Remote URLs in JSX `<img>` tags can be fetched for metadata at build time. Markdown remote images still render, but they do not get build-time dimensions or placeholders.

```mdx
<img src="https://example.com/photo.jpg" alt="Remote photo" />
```

## Click to zoom

Processed images are click-to-zoom by default. Clicking an image expands it into a Medium-style zoomed dialog. To turn this off for a specific image, pass `disableZoom`:

```mdx
<img src="/images/logo.svg" alt="Logo" disableZoom />
```

This is useful for small logos, icons, or images inside a [Marquee](/docs/components/marquee) where zooming on click is undesirable.

## Frames

Wrap images in a `<Frame>` component for a styled border and optional caption:

```mdx
<Frame caption="Dashboard overview">
  ![Dashboard](/images/dashboard.png)
</Frame>
```

See [Frames](/docs/components/frames) for more options.


---
title: Local Imports
url: "https://holocron.so/docs/create/local-imports.md"
description: Import custom React components into MDX pages.
---

# Local Imports

You can import `.tsx`, `.ts`, `.jsx`, `.js`, `.mdx`, or `.md` files directly in your MDX pages. This lets you use custom React components and shared Markdown snippets alongside the built-in MDX components.

## Basic usage

```mdx
import { PricingTable } from '/components/pricing-table'

# Pricing

<PricingTable />
```

## Import resolution

Prefer **relative imports** (`./` or `../`) over absolute imports. They are simpler, work
consistently regardless of `pagesDir`, and match standard JavaScript conventions.

### Relative imports (recommended)

Relative paths resolve from the MDX file's directory:

```mdx
import { Badge } from '../components/badge'
import Guide from './snippets/guide.md'
```

### Absolute imports (starting with `/`)

`/` means the project root. Holocron probes the pages directory first, then the project root:

```mdx
import { Chart } from '/components/chart'
```

If your `pagesDir` is `./pages`, this looks for:

1. `./pages/components/chart.tsx` (pages dir first)
2. `./components/chart.tsx` (project root fallback)

Absolute imports can be ambiguous when `pagesDir` is set. Prefer relative imports instead.

## Importing a README that renders on GitHub too

A popular pattern is importing a repo `README.md` as the docs index so the **same
file renders on both GitHub and the Holocron site**. The README links to other
docs, and those links must work in **both** places.

The rule is simple: **always link to the real target file with a correct relative
path, ending in `.md` or `.mdx`.** Do not reason about slugs, `pagesDir`, or where
`/` ends up. Write the link the way you would for GitHub — a relative path to the
actual file on disk — and Holocron does the rest.

When it inlines the imported file, Holocron **recomputes every relative link to
be correct relative to the importing page**, then **strips the `.md`/`.mdx`
extension**. So a link to a real file lands on that file's page automatically.

<Aside>
  <Tip>
    Because Holocron recomputes the path relative to the importing page, the **same**
    relative href works whether the README sits at the repo root and the page lives
    deep under `pagesDir`, or vice versa.
  </Tip>
</Aside>

### Rules

1. **Link to the real file.** The target must be an actual `.md`/`.mdx` file on
   disk at the relative path you write. That is what GitHub opens and what
   Holocron rewrites. Missing file means a GitHub 404 and a Holocron warning.
2. **Always use a relative path** (`./` or `../`), never absolute. The README
   renders from different base directories on GitHub and Holocron, so an absolute
   `/docs/x` or a guessed path breaks in one of them.
3. **Keep the `.md`/`.mdx` extension.** GitHub needs it to open the file; Holocron
   strips it so the link resolves to the rendered page.
4. **The target file must be rendered by a page in the site.** If the file you
   link to is itself a page (or is imported by one) and that page is in
   `docs.json`, the link resolves. Otherwise the site 404s — add the page and put
   it in navigation.

```diagram
   README.md   [pricing](./docs/pricing.md)   <── correct relative path to a real file
        │
        ├───────────────────────> GitHub opens ./docs/pricing.md
        │
        v  imported into the index page
   Holocron recomputes the path ──> strips .md ──> lands on the page rendering pricing
        │
        v
   /docs/pricing   <── resolves as long as a page renders that file and it is in docs.json
```

## How it works

Import detection is **MDX-driven**, not folder-based. There are no magic `snippets/` or `components/` directories. Any local file can be imported from any location.

At build time, Holocron:

1. Parses each MDX file to extract import statements
2. Resolves the imports against the filesystem
3. Generates a virtual module map so the imports work at render time

At dev time, adding or removing JS/TS importable files triggers HMR automatically. Markdown imports are resolved too, but edits to an MDX page are the most reliable way to refresh newly added Markdown snippets.

## Supported extensions

The following extensions are tried in order: `.tsx`, `.ts`, `.jsx`, `.js`, `.mdx`, `.md`. You do not need to include the extension in the import path.

## Example: shared snippets

A common pattern is a `snippets/` directory for reusable content:

```diagram
my-docs/
├── snippets/
│   └── install-command.tsx
├── getting-started.mdx
└── docs.json
```

```tsx
// snippets/install-command.tsx
export function InstallCommand() {
  return (
    <div>
      <div>npm install @holocron.so/vite</div>
      <div>pnpm add @holocron.so/vite</div>
    </div>
  )
}
```

```mdx
import { InstallCommand } from '/snippets/install-command'

# Getting Started

<InstallCommand />
```


---
title: Diagrams
url: "https://holocron.so/docs/create/diagrams.md"
description: Unicode box-drawing diagrams and the alignment fixer CLI.
---

# Diagrams

Holocron renders fenced code blocks with the `diagram` language hint using special styling. Unicode box-drawing characters and arrows get syntax-colored differently from labels.

````mdx
```diagram
┌───────────────┐          ┌───────────────┐
│   Browser     │────────> │   Server      │
└───────────────┘          └───────────────┘
```
````

## Box-drawing characters

Use Unicode box-drawing characters instead of ASCII `|`, `-`, `+`. They fill the entire terminal cell and render with no visual gaps.

| ASCII | Unicode | Name            |
| ----- | ------- | --------------- |
| `\|`  | `│`     | Vertical line   |
| `--`  | `──`    | Horizontal line |
| `+`   | `┌┐└┘`  | Corners         |
| `+`   | `├┤┬┴┼` | Junctions       |

All four styles are supported: **light** (`┌─┐│`), **heavy** (`┏━┓┃`), **double** (`╔═╗║`), and **rounded** (`╭─╮╰─╯`).

## Layout guidelines

* Diagrams should cover the **full width of the content column**, roughly **94 characters**.
* Never exceed 94 characters per line.
* Use **directional arrows** (`>`, `<`, `v`, `^`, `>`, `<`) on all connections. Never use plain lines without arrowheads.
* Mix plain text labels with boxes. Not everything needs to be inside a box.

## Fixing alignment with the CLI

LLMs frequently produce diagrams where vertical bars, corners, and bottom borders are misaligned by a few characters. The Holocron CLI includes a fixer that detects and repairs these issues automatically.

```bash
# Fix diagrams in a file (writes in-place)
npx -y "@holocron.so/cli" diagrams fix docs/architecture.mdx

# Preview changes without writing
npx -y "@holocron.so/cli" diagrams fix docs/architecture.mdx --dry-run

# Validate only (exits 1 if issues found)
npx -y "@holocron.so/cli" diagrams fix docs/architecture.mdx --check

# Custom max width (default: 94)
npx -y "@holocron.so/cli" diagrams fix docs/architecture.mdx --max-width 120
```

The fixer processes only diagram content inside fenced code blocks. Prose text is never modified.

### What it fixes

* **Right border padding**: content lines where `│` is at the wrong column
* **Bottom border width**: `└─┘` that doesn't match the top `┌─┐`
* **Horizontal dividers**: `├────┤` padded with `─` instead of spaces
* **Cross junctions**: `┼`, `╬`, `╋` preserved when rebuilding dividers
* **Side-by-side boxes**: fixes one box without shifting adjacent boxes on the same row
* **Nested boxes**: inner boxes fixed independently from outer boxes

### What it reports (but cannot auto-fix)

Lines exceeding the max width limit. These require manually shortening content. The CLI prints the line number and current width so you know exactly what to fix.


---
title: Redirects
url: "https://holocron.so/docs/create/redirects.md"
description: Redirect old URLs to new pages without breaking links.
---

# Redirects

When you restructure your docs, redirects keep old links working. Define them in your `docs.json`:

```json
{
  "redirects": [
    { "source": "/old-page", "destination": "/new-page" },
    { "source": "/guides/:slug", "destination": "/docs/:slug" }
  ]
}
```

## GitHub shortcut

Holocron adds a **`/github` shortcut** when a navbar, navigation, logo, or footer link points to GitHub. The temporary redirect preserves the exact configured URL.

```text
/github → https://github.com/owner/repository
```

An authored `github.mdx` page or an explicit redirect whose source matches `/github` **takes priority** over the automatic shortcut.

## Redirect types

### Exact path

```json
{ "source": "/changelog", "destination": "/updates" }
```

### Named parameters

Capture path segments with `:param` and reuse them in the destination:

```json
{ "source": "/api/:version/users", "destination": "/reference/:version/users" }
```

### Trailing wildcard

Match any path under a prefix:

```json
{ "source": "/old-docs/*", "destination": "/docs/:splat" }
```

## Permanent vs temporary

By default, redirects use a **302** (temporary) status code. Set `permanent: true` for a **301**:

```json
{
  "source": "/legacy",
  "destination": "/modern",
  "permanent": true
}
```

## Query string preservation

Query parameters from the original request are passed through to the destination URL.


---
title: Broken Link Detection
url: "https://holocron.so/docs/create/broken-links.md"
description: Holocron warns about internal links pointing to missing pages during build and dev.
---

# Broken Link Detection

Holocron checks every internal link in your MDX files during **build** and **dev server startup**. When a link points to a page that doesn't exist in the navigation tree, a warning is printed to the terminal:

```
▲ holocron broken link /quickstart:12 → /missing-page (no matching page found)
```

The warning includes the **source file**, the **line number**, and the **broken href** so you can fix it quickly.

## What gets checked

Holocron scans two types of links:

**Markdown links**

```mdx
[Getting Started](/getting-started)
[Next section](./next)
[Parent page](../overview)
```

**JSX component hrefs**

```mdx
<Card href="/quickstart">Get Started</Card>
<Tile href="/guides/deploy">Deploy</Tile>
<a href="/api/overview">API Reference</a>
```

Components checked: `a`, `Card`, `Tile`, `Tooltip`, `Badge`.

## What is not checked

These links are **skipped** and never produce warnings:

* **External URLs**: `https://example.com`, `http://...`
* **Anchor-only links**: `#section-id`
* **Special protocols**: `mailto:`, `tel:`, `javascript:`
* **Static files**: paths with file extensions like `/openapi.json`, `/guide.pdf`, `/logo.png`
* **Dynamic JSX expressions**: `<Card href={dynamicUrl}>` (only static string attributes are checked)

## How links are resolved

**Absolute links** like `/getting-started` are matched directly against page hrefs in your navigation tree.

**Relative links** like `./next` or `../overview` are resolved from the linking page's directory. For example, a link `./deploy` inside `guides/setup.mdx` resolves to `/guides/deploy`.

**Hash fragments** and **query strings** are stripped before matching. `/setup#installation` checks whether `/setup` exists as a page.

## Valid link targets

A link is considered valid if it matches any of:

1. A page in the navigation tree
2. A redirect source defined in `redirects`
3. A path declared in `knownPaths`

## Known paths

When your docs app is mounted alongside other routes (API endpoints, dashboards, external apps), links to those routes would trigger false warnings. Use `knownPaths` to declare paths that exist outside of Holocron:

```json
{
  "knownPaths": ["/api/*", "/dashboard", "/blog/*"]
}
```

### Exact paths

`"/dashboard"` matches only `/dashboard`.

### Wildcard prefixes

`"/api/*"` matches `/api/users`, `/api/v2/auth`, and any other path starting with `/api/`.

### Example

A Spiceflow custom entry that serves both docs and an API:

```json
{
  "navigation": [
    { "group": "Docs", "pages": ["index", "quickstart"] }
  ],
  "knownPaths": ["/api/*", "/health"]
}
```

Now a link like `[API Reference](/api/overview)` in your MDX won't trigger a warning, even though `/api/overview` isn't an MDX page.

## When it runs

Link validation runs as part of the **sync phase**, which processes all MDX files and builds the navigation tree. This happens:

* On **`npx vite build`** (production build)
* On **dev server startup** (`npx vite`)
* On **HMR** when an MDX file or the config file changes

The sync phase first parses every page and collects all internal links, then validates them against the fully built navigation tree. This means links are checked after all pages (including OpenAPI-generated ones) are registered.

## Build failures

In production builds, broken links **fail the build** after all errors have been logged. This prevents deploying docs with dead links.

To deploy anyway, set `HOLOCRON_SKIP_BUILD_ERRORS=true`. See [Build Output](/docs/deploy/build-output#build-errors) for details.

## Caching

Link data is cached alongside other page metadata in `dist/holocron-mdx.json`. When an MDX file hasn't changed between builds, its links are restored from cache without re-parsing. Validation still runs on every sync, just the link extraction step is skipped for unchanged files.


---
title: docs.json
url: "https://holocron.so/docs/organize/docs-json.md"
description: "The central config file for navigation, theming, logo, footer, redirects, and site metadata. Compatible with Mintlify's schema."
---

# docs.json

Every Holocron site needs a config file at the project root. Holocron supports three filenames (first found wins):

1. **`docs.json`** (preferred)
2. **`docs.jsonc`** (supports `//` comments)
3. **`holocron.jsonc`** (legacy Holocron name, also supports comments)

All three follow the same schema, which is compatible with Mintlify's `docs.json` for the fields Holocron consumes.

## Minimal config

```json
{
  "name": "My Docs",
  "navigation": [
    {
      "group": "Getting Started",
      "pages": ["index", "quickstart"]
    }
  ]
}
```

## Full config reference

```json
{
  "$schema": "https://holocron.so/docs.json",
  "name": "My Docs",
  "description": "Documentation for my project",
  "colors": { "primary": "#6366f1" },
  "logo": {
    "light": "/logo-light.svg",
    "dark": "/logo-dark.svg"
  },
  "favicon": "/favicon.svg",
  "appearance": { "default": "system" },
  "fonts": { "family": "Inter" },
  "icons": { "library": "lucide" },
  "navbar": {
    "links": [
      { "type": "github", "href": "https://github.com/example/docs" }
    ]
  },
  "banner": {
    "content": "New: [v2.0 is out](/changelog)",
    "dismissible": true
  },
  "navigation": {
    "tabs": [
      {
        "tab": "Documentation",
        "groups": [
          { "group": "Getting Started", "pages": ["index", "quickstart"] }
        ]
      },
      {
        "tab": "API Reference",
        "openapi": "openapi.json"
      }
    ]
  },
  "footer": {
    "socials": {
      "github": "https://github.com/example",
      "x": "https://x.com/example",
      "discord": "https://discord.gg/example"
    }
  },
  "redirects": [
    { "source": "/old", "destination": "/new" }
  ],
  "knownPaths": ["/api/*", "/dashboard"],
  "search": { "prompt": "Search docs..." },
  "seo": {
    "metatags": { "author": "My Company" }
  },
  "assistant": { "enabled": true, "display": "floating", "supportEmail": "support@example.com" },
  "layout": {
    "mode": "compact",
    "maxWidth": 1200,
    "sidebarWidth": 230,
    "columnGap": 60,
    "radius": 10
  }
}
```

## Schema autocomplete

Point your editor at the JSON Schema for autocomplete and validation:

```json
{
  "$schema": "https://holocron.so/docs.json"
}
```

The schema is generated from the Zod definitions in `vite/src/schema.ts`.

## Default page mode

Set **`layout.mode`** to choose the default layout for every page:

```json
{
  "layout": {
    "mode": "compact"
  }
}
```

`compact` keeps the left navigation and removes the right aside. Holocron then defaults **`assistant.display`** to **`floating`** so Ask AI stays available as a bottom pill. Set `display` to `sidebar` to opt out. A page can override the layout with its frontmatter `mode` field. Compact never opens a right rail. Authored asides and API examples render in the main column.

## Passthrough behavior

Holocron's schema accepts unknown fields. Any Mintlify-specific fields that Holocron does not consume, like `api.playground`, are accepted without errors and silently ignored. This means you can use a Mintlify `docs.json` without stripping unknown fields.

## Config normalization

Holocron normalizes shorthand forms into a consistent internal shape:

* **`logo`**: `"/logo.svg"` becomes `{ light: "/logo.svg" }` and is rendered for both modes unless `dark` is provided
* **`favicon`**: `"/favicon.svg"` becomes `{ light: "/favicon.svg", dark: "/favicon.svg" }`
* **`navigation`**: a flat array of groups is wrapped in a default tab
* **`colors`**: missing light or dark values fall back from `primary` when styles are generated
* **`products`**: normalized into `dropdowns` at config time

## Broken link detection

Holocron automatically checks all internal links in your MDX files during build and dev. If a link points to a page that does not exist in the navigation tree, a warning is printed:

```
▲ holocron broken link /quickstart:12 → /missing-page (no matching page found)
```

Links to **redirect sources** and **static files** (paths with file extensions like `.json`, `.pdf`) are not flagged.

### Known paths

When mounting docs alongside other routes (API endpoints, dashboards, external apps), use `knownPaths` to suppress warnings for paths that exist outside of Holocron:

```json
{
  "knownPaths": ["/api/*", "/dashboard", "/blog/*"]
}
```

Supports **exact paths** (`"/dashboard"`) and **prefix patterns** with trailing wildcards (`"/api/*"`).


---
title: Navigation
url: "https://holocron.so/docs/organize/navigation.md"
description: "Configure tabs, groups, nested pages, anchors, and external links that structure the sidebar and top navigation bar."
---

# Navigation

The `navigation` field in `docs.json` controls the sidebar, tab bar, and overall site structure. Holocron supports a Mintlify-compatible subset of navigation shapes.

## Simplest form: flat groups

```json
{
  "navigation": [
    {
      "group": "Getting Started",
      "pages": ["index", "quickstart"]
    },
    {
      "group": "Guides",
      "pages": ["guides/auth", "guides/deployment"]
    }
  ]
}
```

This creates a single sidebar with two collapsible sections.

## Tabs

Tabs split the sidebar into multiple top-level areas. Each tab has its own set of groups:

```json
{
  "navigation": {
    "tabs": [
      {
        "tab": "Documentation",
        "groups": [
          { "group": "Overview", "pages": ["index"] },
          { "group": "Guides", "pages": ["guides/setup"] }
        ]
      },
      {
        "tab": "API Reference",
        "openapi": "openapi.json"
      }
    ]
  }
}
```

Clicking a tab switches the sidebar content. The tab bar appears below the navbar.

### Tab options

| Field    | Type                 | Description                                                                                                              |
| -------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `tab`    | string               | Tab label in the tab bar                                                                                                 |
| `groups` | array                | Sidebar groups inside the tab                                                                                            |
| `pages`  | array                | Flat page list (no groups wrapper)                                                                                       |
| `icon`   | string               | Icon displayed next to the tab label                                                                                     |
| `hidden` | boolean              | Hide from tab bar and sidebar but keep pages indexed for SEO. See [hidden tabs](/docs/organize/hidden-pages#hidden-tabs) |
| `align`  | `"start"` \| `"end"` | Tab alignment in the tab bar                                                                                             |

### Link-only tabs

A tab can be an external link instead of a content section:

```json
{
  "tab": "GitHub",
  "href": "https://github.com/example/docs"
}
```

## Nested groups

Groups can contain other groups for deeper hierarchy:

```json
{
  "group": "Authentication",
  "pages": [
    "auth/overview",
    {
      "group": "Providers",
      "pages": ["auth/github", "auth/google", "auth/email"]
    }
  ]
}
```

## Group options

| Field      | Type    | Description                                      |
| ---------- | ------- | ------------------------------------------------ |
| `group`    | string  | Section title in the sidebar                     |
| `pages`    | array   | Page slugs or nested groups                      |
| `icon`     | string  | Icon displayed next to the group title           |
| `hidden`   | boolean | Hide the entire group from navigation            |
| `expanded` | boolean | Whether the group starts expanded                |
| `root`     | string  | Page slug to use as the group's clickable header |
| `tag`      | string  | Badge label next to the group title              |

## Anchors

Anchors are persistent links rendered in the tab bar. They can be internal or external and appear alongside tabs regardless of which tab is active:

```json
{
  "navigation": {
    "tabs": [
      { "tab": "Docs", "groups": [...] }
    ],
    "global": {
      "anchors": [
        { "anchor": "GitHub", "href": "https://github.com/example", "icon": "github" },
        { "anchor": "Discord", "href": "https://discord.gg/example", "icon": "message-circle" }
      ]
    }
  }
}
```

You can also place `anchors` at the top level of the navigation object (equivalent to `global.anchors`).


---
title: Versions
url: "https://holocron.so/docs/organize/versions.md"
description: Version switcher for multi-version documentation.
---

# Versions

If your project maintains multiple API or SDK versions, you can add a version switcher dropdown to the header. Each version has its own navigation tree.

## Basic setup

```json
{
  "navigation": {
    "versions": [
      {
        "version": "v2",
        "default": true,
        "tabs": [
          {
            "tab": "Docs",
            "groups": [
              { "group": "Getting Started", "pages": ["v2/index", "v2/quickstart"] }
            ]
          }
        ]
      },
      {
        "version": "v1",
        "tabs": [
          {
            "tab": "Docs",
            "groups": [
              { "group": "Getting Started", "pages": ["v1/index", "v1/quickstart"] }
            ]
          }
        ]
      }
    ]
  }
}
```

## How it works

* A native `<select>` dropdown appears in the header (right of the logo).
* Selecting a version navigates to its first page and updates the sidebar.
* If no `index` page exists, the version marked `default: true` determines where `/` redirects to.
* Inner tabs from all versions are flattened into routes, so every page gets a URL.

## Version fields

| Field     | Type    | Description                                            |
| --------- | ------- | ------------------------------------------------------ |
| `version` | string  | Display name in the dropdown                           |
| `default` | boolean | Whether this is the default version                    |
| `tag`     | string  | Badge next to the version name (e.g. "Latest", "Beta") |
| `hidden`  | boolean | Hide from the dropdown                                 |
| `tabs`    | array   | Tabs within this version                               |
| `groups`  | array   | Groups within this version (alternative to tabs)       |
| `pages`   | array   | Pages within this version (flat, no groups)            |


---
title: Dropdowns
url: "https://holocron.so/docs/organize/dropdowns.md"
description: Product or section switcher dropdowns in the header.
---

# Dropdowns

Dropdowns add a `<select>` in the header (next to the version selector if present). Use them for multi-product documentation or section switching.

## Basic setup

```json
{
  "navigation": {
    "dropdowns": [
      {
        "dropdown": "Platform",
        "tabs": [
          {
            "tab": "Docs",
            "groups": [
              { "group": "Overview", "pages": ["platform/index"] }
            ]
          }
        ]
      },
      {
        "dropdown": "CLI",
        "tabs": [
          {
            "tab": "Docs",
            "groups": [
              { "group": "Overview", "pages": ["cli/index"] }
            ]
          }
        ]
      }
    ]
  }
}
```

## Link-only dropdowns

A dropdown entry can be a link instead of a content switcher:

```json
{
  "dropdown": "Changelog",
  "href": "https://example.com/changelog"
}
```

Selecting it opens the URL instead of switching sidebar content.

## Products

The `products` field is an alias for `dropdowns`. It is normalized into dropdowns at config time:

```json
{
  "navigation": {
    "products": [
      {
        "product": "Platform",
        "description": "Main product docs",
        "icon": "layers",
        "groups": [...]
      }
    ]
  }
}
```

## Dropdown fields

| Field      | Type    | Description                      |
| ---------- | ------- | -------------------------------- |
| `dropdown` | string  | Display name in the selector     |
| `icon`     | string  | Icon next to the name            |
| `hidden`   | boolean | Hide from the selector           |
| `href`     | string  | Link-only mode, navigates to URL |
| `tabs`     | array   | Tabs within this dropdown        |
| `groups`   | array   | Groups within this dropdown      |
| `pages`    | array   | Flat pages within this dropdown  |


---
title: Hidden Pages
url: "https://holocron.so/docs/organize/hidden-pages.md"
description: Pages and groups that exist but are not visible in navigation.
---

# Hidden Pages

Sometimes you need a page to be accessible by URL but invisible in the sidebar. Holocron supports hiding at both the page and group level.

## Hidden pages via frontmatter

Set `hidden: true` in the page's frontmatter:

```mdx
---
$schema: https://holocron.so/frontmatter.json
title: Internal API Reference
hidden: true
---
```

The page is still rendered and accessible at its URL, but it does not appear in the sidebar navigation. Hidden pages are also excluded from the sitemap and emitted with `robots=noindex`.

## Hidden groups

Set `hidden: true` on a group in `docs.json`:

```json
{
  "group": "Internal",
  "hidden": true,
  "pages": ["internal/debug", "internal/metrics"]
}
```

All pages in the group are accessible by URL but the group is hidden from the sidebar.

## Hidden tabs

Tabs also support `hidden: true`:

```json
{
  "tab": "Beta",
  "hidden": true,
  "groups": [
    { "group": "Beta Features", "pages": ["beta/feature-x"] }
  ]
}
```

Hidden tabs behave differently from hidden pages:

| Behavior           | Hidden page (`frontmatter`) | Hidden tab |
| ------------------ | --------------------------- | ---------- |
| Visible in sidebar | No                          | No         |
| Visible in tab bar | N/A                         | No         |
| Accessible by URL  | Yes                         | Yes        |
| In sitemap.xml     | **No**                      | **Yes**    |
| In llms.txt        | **No**                      | **Yes**    |
| `robots: noindex`  | **Yes**                     | **No**     |

Hidden **pages** are excluded from search engine indexing. Hidden **tabs** keep all their pages fully indexed; only the UI navigation is removed. This makes hidden tabs the right choice when you want content discoverable by search engines but not shown in the site navigation.

### SEO-only content

Use hidden tabs to publish SEO content that search engines can index without cluttering navigation. This is useful for programmatically generated pages, keyword-targeted landing pages, or AI-generated content:

```json
{
  "navigation": {
    "tabs": [
      { "tab": "Docs", "groups": [...] },
      {
        "tab": "SEO",
        "hidden": true,
        "groups": [
          {
            "group": "Topics",
            "pages": [
              "seo/how-to-authenticate",
              "seo/best-practices",
              "seo/troubleshooting-errors"
            ]
          }
        ]
      }
    ]
  }
}
```

Pages in the hidden tab are rendered at their normal URLs, included in `sitemap.xml` and `llms.txt`, and indexed by search engines. Users who land on these pages via search can still navigate the site normally; the tab just does not appear in the tab bar or sidebar.

## Use cases

* **SEO content** that search engines should index but does not belong in the main navigation
* **Internal pages** that are linked from external tools but should not clutter the sidebar
* **Beta documentation** shared via direct link before it is publicly visible
* **Legacy pages** kept alive for old bookmarks without promoting them in navigation


---
title: Imageboard
url: "https://holocron.so/docs/organize/imageboard.md"
description: Render a folder of images and videos as a masonry moodboard tab.
---

# Imageboard

An imageboard tab turns a **folder of images and videos** into an Instagram-style masonry grid page. Point the tab at a folder, and Holocron walks it recursively, sorts everything by last edit time (newest first), and renders a zoomable, lazy-loaded grid — no sidebars, no MDX files to write.

## Basic setup

```json
{
  "navigation": {
    "tabs": [
      {
        "tab": "Moodboard",
        "icon": "images",
        "imageboard": "./public/moodboard",
        "columns": 3
      }
    ]
  }
}
```

Drop images into `public/moodboard/` and they appear in the grid. Subfolders are included automatically.

<Note>
  Paths are resolved relative to the project root. Folders inside `public/` are served directly; folders outside `public/` work too — images and videos are copied into the build automatically.
</Note>

## How images are processed

Every image goes through the same **build-time pipeline** as MDX images:

* **Dimensions** are read with sharp so the grid reserves each tile's aspect ratio — no layout shift while scrolling.
* A tiny **pixelated placeholder** renders instantly and fades into the real image once loaded.
* Images use native `loading="lazy"`, so offscreen tiles download nothing until scrolled near.
* Clicking a tile opens the **zoom dialog**, same as images in MDX pages.

Videos (`.mp4`, `.webm`, `.mov`, `.mkv`) get their dimensions probed from the container header and render with `preload="metadata"` — only the first frame downloads until the user hits play.

## Sort order

Items are sorted **newest first** by last edit time. Edit times come from git commit history (the last commit that touched each file), so the order is stable across clones and CI deployments where filesystem timestamps are meaningless. Files with uncommitted changes sort by their filesystem modification time instead.

## Options

<ParamField path="imageboard" type="string" required>
  Folder to walk for media, relative to the project root. Example: `"./public/moodboard"` or `"./inspiration"`.
</ParamField>

<ParamField path="base" type="string" default="folder name">
  Slug for the generated page. With `"imageboard": "./public/moodboard"` the page is served at `/moodboard` by default.
</ParamField>

<ParamField path="columns" type="number" default="3">
  Maximum masonry column count on wide viewports. Between 1 and 8. Fewer columns are used automatically on narrow viewports, down to a single column on phones.
</ParamField>

## Layout behavior

The grid uses CSS multi-columns: items flow **top-to-bottom per column**, like a moodboard or Pinterest board. The `columns` value is a maximum — a minimum tile width shrinks the column count fluidly as the viewport narrows, so the grid stays responsive without breakpoint configuration.

The page renders in `custom` mode: only the navbar and footer surround the grid, with no navigation sidebar or table of contents.


---
title: Pages Directory
url: "https://holocron.so/docs/organize/pages-dir.md"
description: Configure where Holocron looks for MDX files.
---

# Pages Directory

By default, Holocron looks for MDX files relative to the **project root** (the directory containing `vite.config.ts`). This matches the Mintlify convention where pages sit alongside `docs.json`.

## Changing the pages directory

If you prefer a subdirectory, pass `pagesDir` to the plugin:

```ts
// vite.config.ts
import { defineConfig } from 'vite'
import { holocron } from '@holocron.so/vite'

export default defineConfig({
  plugins: [
    holocron({ pagesDir: './pages' }),
  ],
})
```

Now page slugs in `docs.json` resolve relative to `./pages/`:

| Slug in docs.json | Resolved file             |
| ----------------- | ------------------------- |
| `index`           | `./pages/index.mdx`       |
| `guides/auth`     | `./pages/guides/auth.mdx` |

## Typical project layouts

### Default (root)

```diagram
my-docs/
├── index.mdx
├── quickstart.mdx
├── guides/
│   └── auth.mdx
├── docs.json
└── vite.config.ts
```

### With pagesDir

```diagram
my-docs/
├── pages/
│   ├── index.mdx
│   ├── quickstart.mdx
│   └── guides/
│       └── auth.mdx
├── docs.json
└── vite.config.ts
```

## Impact on imports

When using local imports in MDX, absolute paths (starting with `/`) probe `pagesDir` first, then project root. See [Local Imports](/docs/create/local-imports) for details.


---
title: Theme
url: "https://holocron.so/docs/customize/theme.md"
description: "Set primary and accent colors, toggle light and dark mode, and override shadcn-compatible CSS variable tokens for full control."
---

# Theme

Holocron uses shadcn-compatible CSS variables. If you already have a shadcn theme, you can port it directly.

## Primary color

Set a brand color in `docs.json`:

```json
{
  "colors": {
    "primary": "#6366f1"
  }
}
```

You can also specify separate light and dark mode accent variants:

```json
{
  "colors": {
    "primary": "#6366f1",
    "light": "#818cf8",
    "dark": "#4f46e5"
  }
}
```

Mintlify names these by the shade, not the mode: `colors.dark` is used in light mode, and `colors.light` is used in dark mode.

## Appearance mode

Control the default color mode and whether users can toggle:

```json
{
  "appearance": {
    "default": "system",
    "strict": false
  }
}
```

| Value          | Behavior                               |
| -------------- | -------------------------------------- |
| `"system"`     | Follow the user's OS setting (default) |
| `"light"`      | Force light mode                       |
| `"dark"`       | Force dark mode                        |
| `strict: true` | Hide the mode toggle                   |

## CSS variable overrides

Holocron's tokens follow the shadcn naming convention. Override them in your own CSS file:

```css
@reference "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));

:root {
  --background: #fafafa;
  --foreground: #0a0a0a;
  --primary: #6366f1;
  --muted-foreground: #737373;
  --border: #e5e5e5;

  @variant dark {
    --background: #0a0a0a;
    --foreground: #fafafa;
    --primary: #818cf8;
    --border: #262626;
  }
}
```

Use `@variant dark` instead of Tailwind's `dark:` for dark mode overrides. This keeps all theme values in CSS variables that adapt automatically. The `@custom-variant dark` line makes those overrides follow Holocron's persisted `.dark` class instead of the browser system media query.

## Available tokens

### Colors

Common color tokens include:

| Token                                        | Purpose                                |
| -------------------------------------------- | -------------------------------------- |
| `--background` / `--foreground`              | Page surface and default text          |
| `--card` / `--card-foreground`               | Elevated surfaces                      |
| `--primary` / `--primary-foreground`         | Brand color and text on it             |
| `--muted` / `--muted-foreground`             | Muted backgrounds and placeholder text |
| `--accent` / `--accent-foreground`           | Hover/active highlights                |
| `--border`                                   | Default border color                   |
| `--border-subtle`                            | Lighter border for subtle dividers     |
| `--sidebar-foreground` / `--sidebar-primary` | Sidebar text and active item           |

### Typography

| Token                   | Default           | Purpose                                                 |
| ----------------------- | ----------------- | ------------------------------------------------------- |
| `--type-body-size`      | `14px`            | Base body text size                                     |
| `--type-heading-1-size` | `16px`            | H1 heading size                                         |
| `--type-heading-2-size` | `16px`            | H2 heading size                                         |
| `--type-heading-3-size` | `16px`            | H3 heading size                                         |
| `--type-small-size`     | `13px`            | Small text                                              |
| `--type-nav-group-size` | `calc(12em / 14)` | Sidebar group labels. Scales with `--sidebar-font-size` |
| `--weight-regular`      | `400`             | Normal text weight                                      |
| `--weight-prose`        | `475`             | Body prose weight                                       |
| `--weight-heading`      | `560`             | Heading weight                                          |

### Code blocks

Style fenced code blocks without targeting internal selectors:

| Token                     | Default       | Purpose                                        |
| ------------------------- | ------------- | ---------------------------------------------- |
| `--code-block-background` | `transparent` | Background color                               |
| `--code-block-border`     | `none`        | Border (e.g. `1px solid var(--border-subtle)`) |
| `--code-block-shadow`     | `none`        | Box shadow around the frame                    |
| `--code-block-radius`     | `0px`         | Corner radius                                  |
| `--code-block-padding-x`  | `0px`         | Horizontal padding                             |
| `--code-block-padding-y`  | `0.5rem`      | Vertical padding                               |

The copy button automatically aligns to the top-right of the padded frame.

```css
:root {
  --code-block-background: var(--muted);
  --code-block-border: 1px solid var(--border-subtle);
  --code-block-radius: var(--radius-md);
  --code-block-padding-x: 8px;
  --code-block-padding-y: 12px;
}
```

### Blockquotes

| Token                       | Default         | Purpose                                    |
| --------------------------- | --------------- | ------------------------------------------ |
| `--blockquote-border-width` | `3px`           | Left border thickness                      |
| `--blockquote-border-color` | `var(--border)` | Left border color                          |
| `--blockquote-font-weight`  | `inherit`       | Font weight (e.g. `500` for bolder quotes) |

### Cards

| Token            | Default                          | Purpose                          |
| ---------------- | -------------------------------- | -------------------------------- |
| `--card-padding` | `16px`                           | Inner padding                    |
| `--card-border`  | `1px solid var(--border-subtle)` | Border around the card frame     |
| `--card-shadow`  | `none`                           | Box shadow around the card frame |

### Tables

| Token            | Default | Purpose                          |
| ---------------- | ------- | -------------------------------- |
| `--table-radius` | `0px`   | Corner radius on markdown tables |

### Sidebar navigation

| Token                         | Default                                                       | Purpose                                                                                                                                          |
| ----------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `--sidebar-font-size`         | `13px` (`14px` at `xl`; `12px` in compact mode)               | Base type size for the left nav. Override on `:root` to change globally. Spacing tokens use `em`, so they scale with this value                  |
| `--sidebar-group-margin-top`  | `calc(12em / 14)`                                             | Top margin above group labels                                                                                                                    |
| `--sidebar-link-radius`       | `calc(6em / 14)`                                              | Corner radius of the hover / active / focus pill behind a nav row                                                                                |
| `--sidebar-row-padding-x`     | `calc(8em / 14)`                                              | Horizontal padding inside a nav row, so its pill extends past the label. Rows cancel it with a negative margin, so the text column does not move |
| `--sidebar-row-padding-y`     | `calc(4em / 14)`                                              | Vertical padding inside a nav row. This is the pill's breathing room above and below the label                                                   |
| `--sidebar-row-gap`           | `calc(4em / 14)`                                              | Space between sidebar rows. Lower this if you raise row padding, so the text-to-text rhythm stays tight                                          |
| `--sidebar-icon-size`         | `calc(12em / 14)`                                             | Width and height of page icons and nested-group chevrons                                                                                         |
| `--sidebar-leading-gap`       | `calc(6em / 14)`                                              | Gap between the leading icon or chevron and the row label                                                                                        |
| `--sidebar-indent`            | `calc(var(--sidebar-icon-size) + var(--sidebar-leading-gap))` | Indentation per nesting level. Equals the leading slot so nested pages align under their group label                                             |
| `--sidebar-active-background` | `transparent`                                                 | Background pill behind the deepest active item (TOC heading when expanded, else page link)                                                       |
| `--sidebar-hover-background`  | `var(--accent)`                                               | Hover background on sidebar links, group toggles, and TOC headings                                                                               |
| `--search-input-radius`       | `var(--radius-xl)`                                            | Corner radius of the sidebar search input                                                                                                        |

### Spacing

| Token           | Default | Purpose                            |
| --------------- | ------- | ---------------------------------- |
| `--prose-gap`   | `20px`  | Between elements inside a section  |
| `--section-gap` | `48px`  | Between heading-delimited sections |
| `--list-gap`    | `8px`   | Between list items                 |

## Semantic colors

Holocron also defines semantic colors for callouts and badges: `--blue`, `--green`, `--yellow`, `--orange`, `--red`, `--purple`. These adapt to dark mode automatically.


---
title: Fonts
url: "https://holocron.so/docs/customize/fonts.md"
description: Custom fonts for headings and body text.
---

# Fonts

Holocron uses Inter by default. The default Inter files are bundled with the site and served from the same origin, so a fresh site does not make third-party font requests.

Use the `fonts` field in `docs.json` when you want a different typeface. Family names without `source` load from Google Fonts. Add `source` when you want to self-host a custom font from `public/`.

## Using Google Fonts

Set the `family` field to any [Google Fonts](https://fonts.google.com) family name. Holocron automatically generates the stylesheet link and preconnect tags for you.

```json docs.json
{
  "fonts": {
    "family": "DM Sans"
  }
}
```

This applies **DM Sans** to all text on the site, including headings, body, sidebar, and navigation.

## Separate heading and body fonts

Use the `heading` and `body` fields to set different fonts for each. For example, a serif font for headings with a clean sans-serif for body text:

```json docs.json
{
  "fonts": {
    "heading": { "family": "Fraunces", "weight": 700 },
    "body": { "family": "DM Sans", "weight": 400 }
  }
}
```

The top-level `family` sets the base font for everything. `heading` and `body` override it for their respective contexts. If you only set `heading`, body text still uses the base font.

## Self-hosted fonts

Place your font file in `public/` and reference it with `source`:

```json docs.json
{
  "fonts": {
    "family": "My Custom Font",
    "source": "/fonts/custom-font.woff2",
    "format": "woff2"
  }
}
```

Self-hosted fonts skip Google Fonts entirely. No third-party requests are made.

## Font fields

| Field     | Type                  | Description                                             |
| --------- | --------------------- | ------------------------------------------------------- |
| `family`  | string                | Font family name. Google Fonts names load automatically |
| `weight`  | number                | Font weight (e.g. 400, 700)                             |
| `source`  | string                | URL or local path to a font file                        |
| `format`  | `"woff"` \| `"woff2"` | Font file format. Required when using a local `source`  |
| `heading` | object                | Override font for headings only                         |
| `body`    | object                | Override font for body text only                        |

The `heading` and `body` fields accept the same properties: `family`, `weight`, `source`, and `format`.


---
title: Icons
url: "https://holocron.so/docs/customize/icons.md"
description: Configure the icon library for your docs site.
---

# Icons

Icons appear in the sidebar (next to pages and groups), in components like Cards, and in the navbar.

## Setting the icon library

```json
{
  "icons": { "library": "lucide" }
}
```

Supported libraries:

| Library       | Description                     |
| ------------- | ------------------------------- |
| `fontawesome` | Font Awesome free set (default) |
| `lucide`      | Clean, consistent icons         |

## Prefixed icon names

Use **prefixed icon names** so the source library is explicit. This also lets you mix icons
from different libraries in the same project:

```jsonc
// Lucide icons (recommended)
"icon": "lucide:rocket"
"icon": "lucide:shield"

// Font Awesome icons
"icon": "fontawesome:brands:github"
"icon": "fontawesome:solid:compass"

// Plain names resolve against the configured library
"icon": "rocket"  // resolves to lucide:rocket if "icons.library": "lucide"
```

## Using icons in frontmatter

Set an icon on any page via frontmatter:

```mdx
---
$schema: https://holocron.so/frontmatter.json
title: Authentication
icon: lucide:lock
---
```

The icon name is resolved against the configured library if no prefix is given.

## Local SVG files

Put an **SVG** in `public/` and set `icon` on the **page** to a root-absolute path (`/icons/vercel.svg`). Holocron looks in `public/`, then the project root. Relative paths like `./icons/vercel.svg` resolve from those same directories, not from the MDX file. A `../` path is rejected and fails the production build. Holocron inlines the SVG into the same atlas as Lucide, so it inherits **`currentColor`** and sidebar size. A missing file also fails the production build.

```
public/
  icons/
    vercel.svg
```

```mdx
---
$schema: https://holocron.so/frontmatter.json
title: Vercel deployments
sidebarTitle: Vercel
icon: /icons/vercel.svg
---

Connect Vercel and check the latest production deploy.
```

<Card title="Vercel" icon="/icons/vercel.svg">
  `icon: /icons/vercel.svg`
</Card>

The SVG should use **`currentColor`** for `fill` and `stroke`. Remote `https://` icons still render as images.

You can also set a local SVG on a **group** in `docs.json`:

```json
{
  "group": "Integrations",
  "icon": "/icons/slack.svg",
  "pages": ["integrations/slack"]
}
```

## Using icons in navigation

Groups and anchors accept an `icon` field:

```json
{
  "group": "Security",
  "icon": "lucide:shield",
  "pages": ["security/overview"]
}
```

## Icon object form

For more control, use the object form to specify a library or style per icon. Font Awesome currently resolves the `solid`, `regular`, and `brands` styles:

```json
{
  "icon": {
    "name": "github",
    "library": "fontawesome",
    "style": "brands"
  }
}
```

## Finding icon names

To browse all available icon names, fetch the schema JSONs:

```bash
# All lucide icon names
curl -s https://holocron.so/schemas/lucide-icons.json | jq '.enum[:10]'

# All fontawesome icon names
curl -s https://holocron.so/schemas/fontawesome-icons.json | jq '.enum[:10]'
```

## Icon colors

Add `iconColor` to give icons a distinct color. This works everywhere icons are supported: page frontmatter, groups, tabs, anchors, navbar links, and dropdowns.

**Named colors** use the built-in editorial palette, which adapts to light and dark mode automatically:

| Color    | Example                 |
| -------- | ----------------------- |
| `green`  | `"iconColor": "green"`  |
| `blue`   | `"iconColor": "blue"`   |
| `red`    | `"iconColor": "red"`    |
| `purple` | `"iconColor": "purple"` |
| `orange` | `"iconColor": "orange"` |
| `yellow` | `"iconColor": "yellow"` |
| `pink`   | `"iconColor": "pink"`   |

You can also pass any **CSS color string** directly (hex, rgb, hsl):

```json
{ "iconColor": "#e11d48" }
```

### In frontmatter

```mdx
---
$schema: https://holocron.so/frontmatter.json
title: Authentication
icon: lock
iconColor: green
---
```

### In docs.json

Works on groups, tabs, anchors, navbar links, and dropdowns:

```json
{
  "navigation": {
    "tabs": [
      {
        "tab": "API Reference",
        "icon": "code",
        "iconColor": "blue",
        "groups": [...]
      }
    ],
    "global": {
      "anchors": [
        {
          "anchor": "Discord",
          "href": "https://discord.gg/example",
          "icon": "message-circle",
          "iconColor": "purple"
        }
      ]
    }
  }
}
```

### Desaturation

Sidebar page icons with a color are **desaturated 30%** by default. They go to full saturation when the page is **active** or on **hover**. This keeps the sidebar visually clean while still showing color.

## Icon consistency

When adding icons, apply them **consistently across all siblings** at the same level. Inconsistent
icon usage looks unfinished and breaks visual rhythm.

* **Tabs**: if one tab has an icon, every tab must have an icon.
* **Groups**: if one sidebar group has an icon, every sibling group in the same tab should too.
* **Anchors**: if one anchor has an icon, all anchors must.
* **Pages**: if one page in a group has a frontmatter `icon`, all pages in that group should.
* **Cards**: if one `<Card>` in a `<CardGroup>` has an `icon` prop, every card in that group must.

If a group has an icon, avoid using the **same icon** on the first page in that group. It looks
like a duplicate in the navigation tree.

## Build-time atlas

Holocron collects all referenced icon names at build time and generates an atlas. Only the icons you actually use are included in the client bundle.


---
title: Logo and Favicon
url: "https://holocron.so/docs/customize/logo-and-favicon.md"
description: Set your brand logo and favicon.
---

# Logo and Favicon

## Logo

The logo appears in the top-left corner of the navbar. You can set a single logo or separate ones for light and dark mode:

### Single logo

```json
{
  "logo": "/logo.svg"
}
```

### Separate light and dark logos

```json
{
  "logo": {
    "light": "/logo-light.svg",
    "dark": "/logo-dark.svg"
  }
}
```

The `light` logo is shown in light mode and the `dark` logo is shown in dark mode. If you only provide one logo, Holocron reuses it in both modes.

### Logo link

By default, clicking the logo navigates to `/`. Set a custom target:

```json
{
  "logo": {
    "light": "/logo-light.svg",
    "dark": "/logo-dark.svg",
    "href": "https://example.com"
  }
}
```

## Favicon

```json
{
  "favicon": "/favicon.svg"
}
```

Like logo, you can set separate favicons for light and dark mode:

```json
{
  "favicon": {
    "light": "/favicon-light.svg",
    "dark": "/favicon-dark.svg"
  }
}
```

Place logo and favicon files in the `public/` directory.


---
title: "Navbar, Footer, and Banner"
url: "https://holocron.so/docs/customize/navbar-footer-banner.md"
description: "Configure top navigation links, footer socials, and site-wide banners."
---

# Navbar, Footer, and Banner

## Navbar

The navbar sits at the top of every page. Configure links and a primary CTA button:

```json
{
  "navbar": {
    "links": [
      { "type": "github", "href": "https://github.com/example/docs" },
      { "label": "Blog", "href": "https://example.com/blog" }
    ],
    "primary": {
      "type": "button",
      "label": "Get Started",
      "href": "/quickstart"
    }
  }
}
```

### Link types

Known `type` values: `github`, `discord`, `slack`, `button`, `link`. The type controls the default icon and label. You can always override with explicit `label` and `icon` fields.

## Footer

### Social links

Add social links to the footer with recognized platform names:

```json
{
  "footer": {
    "socials": {
      "github": "https://github.com/example",
      "x": "https://x.com/example",
      "discord": "https://discord.gg/example",
      "linkedin": "https://linkedin.com/company/example"
    }
  }
}
```

Supported platforms: `x`, `twitter`, `github`, `discord`, `slack`, `linkedin`, `youtube`, `instagram`, `facebook`, `medium`, `telegram`, `bluesky`, `threads`, `reddit`, `hacker-news`, `podcast`, `website`.

### Footer link columns

Add organized link columns (up to 4):

```json
{
  "footer": {
    "links": [
      {
        "header": "Product",
        "items": [
          { "label": "Pricing", "href": "/pricing" },
          { "label": "Changelog", "href": "/changelog" }
        ]
      },
      {
        "header": "Resources",
        "items": [
          { "label": "Blog", "href": "/blog" },
          { "label": "Community", "href": "https://discord.gg/example" }
        ]
      }
    ]
  }
}
```

## Banner

Display a site-wide banner at the top of every page:

```json
{
  "banner": {
    "content": "New: [v2.0 is here](/changelog). Check out the new features!",
    "dismissible": true
  }
}
```

The `content` field supports basic Markdown links (`[text](url)`). Set `dismissible: true` to show a close button.


---
title: Custom CSS
url: "https://holocron.so/docs/customize/custom-css.md"
description: "Override styles without breaking Holocron's Tailwind setup."
---

# Custom CSS

Holocron includes Tailwind CSS automatically. You can add your own styles on top.

## How to load your CSS

### Default Holocron site

Create `style.css` at your project root. Holocron auto-detects the first matching file and imports it after its own styles.

```diagram
my-docs/
├── docs.json
├── style.css
└── index.mdx
```

### With custom entry and another filename

If you use a [custom entry](/docs/custom-entry) and your CSS file has a different name, import it in your server file:

```tsx
// src/server.tsx
import './style.css'
```

## Dark mode overrides

Use `@variant dark` inside your CSS instead of Tailwind's `dark:` utility classes:

```css
/* style.css */
@reference "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));

:root {
  --card: #ffffff;
  --card-foreground: #0a0a0a;

  @variant dark {
    --card: #1a1a1a;
    --card-foreground: #fafafa;
  }
}
```

This keeps dark mode values colocated with their light mode counterparts in CSS variables. Holocron toggles dark mode with the `.dark` class on `<html>`, so any CSS file using `@variant dark` must define the same `@custom-variant dark` directive.

## Responsive overrides

You can also change variables by breakpoint:

```css
/* style.css */
:root {
  --bleed: 16px;

  @variant lg {
    --bleed: 32px;
  }
}
```

## Do not import Tailwind again

Holocron already adds `@import 'tailwindcss'` internally. If your CSS also imports it, you get duplicate style layers that break layout and cascade order.

Instead, use **`@reference`** to access Tailwind's theme variables, `@apply`, and `@variant` without emitting duplicate styles:

```css
/* style.css */
@reference "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));

:root {
  --primary: #e11d48;
  --background: #fafafa;

  @variant dark {
    --background: #0a0a0a;
    --primary: #fb7185;
  }
}
```

## Best practices

**Prefer CSS variables over class selectors.** Holocron exposes CSS variables for colors, typography, spacing, code blocks, and layout. Override these in `:root` for clean, stable customization that survives Holocron updates. See [Theme](/docs/customize/theme) for the full list.

```css
/* Stable — uses documented CSS variables */
:root {
  --code-block-background: var(--muted);
  --code-block-border: 1px solid var(--border-subtle);
  --weight-heading: 700;
}
```

**Avoid targeting internal class names and DOM structure.** Selectors like `.slot-sidebar-left nav > div:first-child > a` or `figure[class~='group/code'] button[aria-label='Copy code']` are coupled to Holocron's internal markup. These will break when Holocron refactors its components; there is no stability guarantee for internal class names, DOM nesting, or aria labels.

If you do need a class-based override as a last resort, keep it isolated and add a comment noting it may break on upgrade:

```css
/* FRAGILE: targets internal sidebar DOM, may break on Holocron updates */
.slot-sidebar-left nav a {
  border-radius: var(--radius-sm);
}
```


---
title: Layout
url: "https://holocron.so/docs/customize/layout.md"
description: "Control the page layout, grid width, and page modes."
---

## Page modes

Every page can set a **mode** in its frontmatter to control how much of the editorial layout is rendered.

| Mode      | Left sidebar | Right aside | Editorial grid | Use case                         |
| --------- | ------------ | ----------- | -------------- | -------------------------------- |
| `default` | Yes          | Yes         | Yes            | Standard docs pages              |
| `compact` | Yes          | No          | Yes (2-column) | Focused docs with a narrow frame |
| `center`  | No           | Yes         | Yes (2-column) | Focused content without nav      |
| `custom`  | No           | No          | No             | Landing pages, custom layouts    |

`wide` and `frame` are accepted for Mintlify compatibility and alias to `default`.

### Compact mode

Use **`compact`** to keep the left navigation while removing the optional right aside. The content column keeps its normal reading width, so the full page frame becomes narrower and the header aligns with the content on the right. Compact also sets **`--sidebar-font-size`** to **12px** (the default is 13px, 14px at `xl`). Override that token on `:root` if you want a different size.

```yaml
---
$schema: https://holocron.so/frontmatter.json
mode: "compact"
---
```

Set the same mode in **`docs.json`** to make it the default for the full site. Compact mode removes the right aside, so Holocron defaults **`assistant.display`** to **`floating`**. Ask AI stays available as a bottom pill. Set `display` to `sidebar` if you do not want the pill.

```json
{
  "layout": {
    "mode": "compact"
  }
}
```

See [AI Assistant](/docs/ai/assistant) for the floating pill. Page frontmatter overrides the site default. Use `mode: "default"` on a specific page to restore the full three-column layout.

Compact **never** opens a right rail. Authored asides, API examples, and table-of-contents panels render in the **main column**. The sidebar Ask AI widget stays hidden.

With the default geometry, compact mode derives a **910px frame** from the existing layout values:

```text
1200px max width - 230px right aside - 60px column gap = 910px
```

There is no separate compact width setting. Changes to `layout.maxWidth` or `layout.columnGap` update the compact frame automatically.

### Custom mode

Set `mode: "custom"` to strip the editorial layout entirely. Only the **navbar**, **tab bar**, **footer**, and **mobile navigation** are rendered. The content area is a plain container where you control everything with your own HTML and Tailwind classes.

```yaml
---
$schema: https://holocron.so/frontmatter.json
mode: "custom"
---
```

This is useful for landing pages, pricing pages, or any page where you want full control over the layout. Standard MDX components like code blocks, callouts, and icons still work inside custom pages.

### `maxWidth`

Use the `maxWidth` frontmatter field to constrain the content container width in pixels. The navbar stays full-width; only the content area is narrowed and centered.

```yaml
---
$schema: https://holocron.so/frontmatter.json
mode: "custom"
maxWidth: 700
---
```

### Example landing page

```mdx
---
mode: "custom"
maxWidth: 800
---

<div className="flex flex-col items-center py-24 gap-6 text-center">

<h1 className="text-5xl font-bold tracking-tight">
  Your Product Name
</h1>

<p className="text-lg text-muted-foreground max-w-xl">
  A short description of what your product does and why it matters.
</p>

<a href="./quickstart" className="no-underline rounded-lg bg-primary px-5 py-2.5 text-sm font-medium text-primary-foreground">
  Get Started
</a>

</div>
```

## Full-width layout

By default Holocron caps the page grid at **1200px**. The left sidebar, content column, and right sidebar all live inside that constraint.

You can make the layout span the full viewport by overriding a single CSS variable.

## Override `--grid-max-width`

In your `style.css` file (see [Custom CSS](/docs/customize/custom-css)):

```css
/* style.css */
:root {
  --grid-max-width: 2600px;
}
```

This pushes the left and right sidebars toward the screen edges. The content column grows to fill the extra space, up to its **720px** cap. Any remaining width is distributed as gap between the three columns.

```diagram
Default (1200px max)
┌────────────────────────────────────────────────────────────────────────────┐
│              ┌───────┐    ┌──────────────┐    ┌───────┐                    │
│              │  nav  │    │   content    │    │ aside │                    │
│              └───────┘    └──────────────┘    └───────┘                    │
└────────────────────────────────────────────────────────────────────────────┘

Full-width (2600px max)
┌────────────────────────────────────────────────────────────────────────────┐
│ ┌───────┐              ┌──────────────┐              ┌───────┐             │
│ │  nav  │              │   content    │              │ aside │             │
│ └───────┘              └──────────────┘              └───────┘             │
└────────────────────────────────────────────────────────────────────────────┘
```

## How the grid works

Holocron's page grid is controlled by four CSS variables:

| Variable               | Default  | Description                      |
| ---------------------- | -------- | -------------------------------- |
| `--grid-max-width`     | `1200px` | Overall page cap                 |
| `--grid-nav-width`     | `230px`  | Left sidebar (table of contents) |
| `--grid-sidebar-width` | `230px`  | Right sidebar (aside content)    |
| `--grid-gap`           | `60px`   | Gap between columns              |

The **content column width** is derived automatically:

```
content = min(720px, max-width - nav - sidebar - 2 × gap)
```

This means increasing `--grid-max-width` does not make the content column infinitely wide. It grows until it hits the 720px cap, then the extra space becomes gap.

API pages bump `--grid-sidebar-width` to **460px** when the aside contains `RequestExample` or `ResponseExample`. Use `<Aside width={N}>` for any other fixed size. See [Aside](/docs/components/aside).

## Aside height and section spacing

A non-full `<Aside>` shares its vertical space with the section it belongs to (the content between two headings). The section row expands to fit whichever is taller: the main content or the aside. If the aside callout is taller than the section text, you will see extra whitespace below the main content.

To avoid this, keep aside callouts **short** and only place them in sections with enough body text to match or exceed the aside height.

## True edge-to-edge

If you want the grid to always fill the viewport regardless of screen size, use `100vw`:

```css
/* style.css */
:root {
  --grid-max-width: 100vw;
}
```

This removes the max-width constraint entirely. The grid stretches on every screen.


---
title: Bleed
url: "https://holocron.so/docs/customize/bleed.md"
description: Let content extend beyond the prose column for more visual impact.
---

# Bleed

Bleed lets content extend beyond the prose column into the page margins. This gives images, videos, code blocks, and embeds more breathing room without changing the overall grid layout.

## How it works

Holocron defines a `--bleed` CSS variable that controls how far content extends past the prose column. It is **0px on mobile** and **32px on desktop** (≥ 1080px). The `.bleed` CSS class applies negative left and right margins equal to `--bleed`, making the element wider than its parent.

```diagram
  Mobile (< 1080px)                      Desktop (≥ 1080px)

  ┌──────────────────────┐            ┌──────────────────────────────┐
  │ prose column         │            │       prose column           │
  │ ┌──────────────────┐ │         ┌──┼──────────────────────────────┼──┐
  │ │ bleed element    │ │         │  │  bleed element extends       │  │
  │ │ (same width)     │ │         │  │  past both edges <──────────>│  │
  │ └──────────────────┘ │         └──┼──────────────────────────────┼──┘
  └──────────────────────┘            └──────────────────────────────┘
```

## Default bleed behavior

Some elements bleed automatically:

* **Code blocks** bleed into the right margin by default so code text lines up with the prose left edge. You can control this with the `bleed` meta option (see [Code Blocks](/docs/create/code#bleed)).
* **Images** are wrapped in a `<Bleed>` component automatically and extend into both margins.

## Adding bleed to any element

Wrap any content in a `div` with `className="bleed"` to make it extend into the margins. This is useful for **videos, iframes, embeds, and large visuals** that benefit from extra width.

### YouTube embed

```mdx
<div className='bleed'>
<iframe
  width="100%"
  height="400"
  src="https://www.youtube.com/embed/dQw4w9WgXcQ?controls=0&modestbranding=1&rel=0&showinfo=0&iv_load_policy=3"
  title="Video title"
  frameBorder="0"
  allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
  allowFullScreen
  style={{ borderRadius: '8px' }}
/>
</div>
```

### Framed video

Combine `bleed` with `Frame` for a captioned video that extends into the margins:

```mdx
<Frame caption="Product demo" className='bleed'>
  <video src="/videos/demo.mp4" controls width="100%" />
</Frame>
```

### Wide image

Images already bleed by default, but if you have a custom image layout (like a side-by-side comparison), wrap it:

```mdx
<div className='bleed'>
  <Columns cols={2}>
    <img src="/images/before.png" alt="Before" />
    <img src="/images/after.png" alt="After" />
  </Columns>
</div>
```

## `no-bleed` — keeping content inside containers

Container components like **Callout**, **Accordion**, **Expandable**, **Panel**, **Steps**, and **Card** apply the `no-bleed` class automatically. This sets `--bleed: 0px` for all descendants so code blocks, lists, and images stay inside the container frame.

You can also use `no-bleed` yourself on any wrapper:

```mdx
<div className='no-bleed'>
  Code blocks and images inside here will not bleed.
</div>
```

## Overriding `--bleed` in custom CSS

Change the bleed distance globally in your `style.css`:

```css
/* style.css */
:root {
  --bleed: 0px;

  @variant lg {
    --bleed: 48px; /* wider bleed on desktop */
  }
}
```

Set `--bleed: 0px` at all breakpoints to disable bleed entirely.


---
title: Custom Entry
url: "https://holocron.so/docs/custom-entry.md"
description: "Mount Holocron docs alongside your own API routes, pages, and middleware in a single Spiceflow app."
---

# Custom Entry

Holocron can be mounted as a **child app** inside your existing Spiceflow project. This lets you ship docs, API routes, auth, webhooks, and custom pages all from a single server.

## When to use this

* You already have a Spiceflow app and want to add a `/docs` section
* You need middleware (auth, logging, headers) to wrap both your routes and the docs
* You want API routes like `/api/chat` living next to your documentation

## Keep docs in a subfolder

When mounting Holocron alongside your own app, **always put MDX files inside a subfolder** like `docs/`. This way all documentation lives under `/docs/*` and won't collide with your app routes like `/api`, `/login`, `/dashboard`, or `/pricing`.

Without a subfolder, a page like `configuration.mdx` maps to `/configuration`, which could easily conflict with a current or future app route. With a `docs/` prefix it becomes `/docs/configuration` and stays cleanly isolated. As your app grows you never have to worry about a new feature route clashing with a docs page.

```diagram
my-project/
├── docs/               ← all MDX pages live here
│   ├── getting-started.mdx
│   └── configuration.mdx
├── docs.json
├── server.tsx
├── vite.config.ts
└── package.json
```

Reference pages in `docs.json` with the `docs/` prefix:

```json
{
  "navigation": [
    {
      "group": "Guides",
      "pages": ["docs/getting-started", "docs/configuration"]
    }
  ]
}
```

Page slugs map directly to file paths. `docs/getting-started` resolves to `docs/getting-started.mdx` and is served at `/docs/getting-started`.

## Set a `docs/` base for OpenAPI and Changelog tabs

OpenAPI and Changelog tabs generate their pages from a **`base`** slug prefix, not from files on disk. The defaults put them at the **root** of your domain: OpenAPI endpoints land at `/api/*` and the changelog lands at `/changelog`.

In a standalone docs site that is fine. **In a custom entry app it is a problem**, because those generated pages now sit at the root of your real product domain right next to `/api`, `/login`, and `/dashboard`. The OpenAPI default is the most dangerous one: `base: "api"` means the generated reference pages collide directly with your actual API routes like `/api/users`.

Always prefix these `base` values with `docs/` so the generated pages stay under your docs namespace:

```jsonc
{
  "navigation": {
    "tabs": [
      {
        "tab": "API Reference",
        "openapi": "openapi.json",
        // ✅ generates /docs/api/* instead of /api/* (which collides with your real API)
        "base": "/docs/api"
      },
      {
        "tab": "Changelog",
        "changelog": "https://github.com/acme/acme",
        // ✅ generates /docs/changelog instead of /changelog
        "base": "/docs/changelog"
      }
    ]
  }
}
```

<Aside>
  <Warning>
    The OpenAPI **`base` defaults to `"api"`**, so without an override your generated reference pages are served at `/api/*` and will collide with your real API routes. Always set `base: "/docs/api"` (or another `docs/`-prefixed slug) in a custom entry app.
  </Warning>
</Aside>

This keeps every part of the docs, MDX pages, OpenAPI reference, and the changelog, under one predictable `/docs/*` namespace, leaving your app's root free for product routes.

## Setup

Pass `entry` to the holocron plugin pointing to your Spiceflow server file:

```ts
// vite.config.ts
import { defineConfig } from 'vite'
import { holocron } from '@holocron.so/vite'

export default defineConfig({
  plugins: [
    holocron({ entry: './src/server.tsx' }),
  ],
})
```

Then in your server file, import the holocron app and mount it with `.use()`:

```tsx
// src/server.tsx
import { Spiceflow } from 'spiceflow'
import { app as holocronApp } from '@holocron.so/vite/app'

export const app = new Spiceflow()
  // your middleware runs on every request, including docs pages
  .use(async ({ request }, next) => {
    const res = await next()
    if (res) res.headers.set('x-custom-header', 'yes')
    return res
  })
  // your own API routes
  .get('/api/hello', () => ({ hello: 'world' }))
  .get('/api/echo/:name', ({ params }) => ({ name: params.name }))
  // your own pages with your own layouts
  .layout('/dashboard', ({ children }) => (
    <html lang='en'>
      <head><title>Dashboard</title></head>
      <body>{children}</body>
    </html>
  ))
  .page('/dashboard', () => <h1>My Dashboard</h1>)
  // mount holocron last — it handles all docs pages
  .use(holocronApp)

void app.listen(3000)
```

Holocron registers routes for every page in your `docs.json` navigation. Your own routes take priority because they're registered first.

## Middleware

Middleware registered with `.use()` before `.use(holocronApp)` runs on **every request**, including doc pages. This is useful for auth checks, analytics headers, or request logging.

```tsx
export const app = new Spiceflow()
  .use(async ({ request }, next) => {
    console.log(request.method, request.url)
    return next()
  })
  .use(holocronApp)
```

## Testing with Vitest

Your custom entry imports `@holocron.so/vite/app`. The `holocron()` plugin **aliases** that import to its source app and provides the `virtual:holocron-config`, `virtual:holocron-navigation`, `virtual:holocron-mdx`, and `virtual:holocron-modules` modules. In normal `dev` and `build` the plugin is always present, so this just works.

In tests it is easy to drop the plugin by accident. If you build a separate Vitest config without `holocron()`, importing your server file fails at module load:

```bash
Error: Cannot find package 'virtual:holocron-config'
```

The fix is to **keep `holocron()` in your test Vite config** so the alias and virtual modules exist there too. Point its `entry` at the same server file you ship.

```ts
// vite.config.ts
import { defineConfig } from 'vite'
import { holocron } from '@holocron.so/vite'

export default defineConfig({
  plugins: [
    holocron({ entry: './src/server.tsx' }),
  ],
  test: {
    // your Vitest options
  },
})
```

### Cloudflare Workers tests

When testing a Workers custom entry with `@cloudflare/vitest-pool-workers`, the pool wants the raw `react` and `spiceflow` plugins so it can run your app inside `workerd`, while `cloudflare()` must be **off** in tests (the pool manages `workerd` itself). You still need `holocron()` for the alias and virtual modules.

These coexist. The `holocron()` plugin **detects** an already-installed React, Spiceflow, Tailwind, or Cloudflare plugin and skips adding its own duplicate, so listing them yourself is safe.

```ts
// vite.config.ts
import { defineConfig } from 'vite'
import { cloudflareTest } from '@cloudflare/vitest-pool-workers'
import react from '@vitejs/plugin-react'
import { spiceflowPlugin } from 'spiceflow/vite'
import { holocron } from '@holocron.so/vite'

const isTest = !!process.env.VITEST

export default defineConfig({
  plugins: [
    isTest ? cloudflareTest({ wrangler: { configPath: './wrangler.test.jsonc' } }) : null,
    // In tests: raw react + spiceflow so the pool can run the app, plus holocron
    // for the `@holocron.so/vite/app` alias and `virtual:*` modules. holocron
    // sees the raw plugins and skips re-adding them.
    react(),
    spiceflowPlugin({ entry: './src/server.tsx' }),
    holocron({ entry: './src/server.tsx' }),
  ],
})
```

<Aside>
  <Warning>
    Do not load `cloudflare()` (the deploy plugin) in test mode. Both it and the Workers test pool manage `workerd`, and running them together conflicts. Keep `cloudflare()` for `dev`/`build` only and use `cloudflareTest()` for tests.
  </Warning>
</Aside>

## Custom CSS

Holocron includes Tailwind CSS automatically. **Do not add `@import 'tailwindcss'` in your own CSS files.** A second import creates duplicate style layers that break layout, spacing, and cascade order.

## Tailwind references

Use **`@reference`** instead of `@import`. It gives your CSS access to Tailwind's theme variables, `@apply`, and `@variant` without emitting duplicate styles:

```css
/* src/globals.css */
@reference "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));

:root {
  --primary: #e11d48;
  --background: #fafafa;

  @variant dark {
    --background: #0a0a0a;
    --primary: #fb7185;
  }
}
```

## Dark mode variant limitation

`@custom-variant dark` must be repeated in every Tailwind-processed CSS file that uses `@variant dark`. This is a Tailwind limitation: `@custom-variant` is a **compile-time directive**, not a runtime CSS variable or inherited browser setting.

Holocron defines the dark variant in its own stylesheet, but that only affects CSS compiled in Holocron's Tailwind processing context. Your custom entry CSS is processed as a separate stylesheet, so Tailwind does not automatically know that Holocron uses class-based dark mode.

## Required dark variant

If you omit this line:

```css
@custom-variant dark (&:where(.dark, .dark *));
```

then `@variant dark` in your CSS can compile to Tailwind's default dark behavior, which may follow the browser system media query. That makes your app theme disagree with Holocron's persisted toggle, because Holocron stores the selected theme by adding or removing `.dark` on `<html>`.

## Shared theme cookie

Holocron persists the selected mode in the **`color-theme`** cookie and mirrors it on `<html class="dark">`. If your app has its own theme toggle outside the docs layout, use the same cookie so both your pages and Holocron pages share one persisted state:

```tsx
'use client'

function setTheme(theme: 'light' | 'dark') {
  document.documentElement.classList.toggle('dark', theme === 'dark')
  document.cookie = `color-theme=${theme}; Path=/; Max-Age=31536000; SameSite=Lax`
}

export function ThemeToggle() {
  function toggle() {
    const isDark = document.documentElement.classList.contains('dark')
    setTheme(isDark ? 'light' : 'dark')
  }

  return <button onClick={toggle}>Toggle theme</button>
}
```

## Server-rendered theme class

If your custom pages render their own `<html>` shell, read the **same cookie** on the server and set the initial class before the page paints:

```tsx
function getInitialThemeClass(request: Request) {
  const cookie = request.headers.get('cookie') ?? ''
  return /(?:^|;\s*)color-theme=dark(?:;|$)/.test(cookie) ? 'dark' : undefined
}

export const app = new Spiceflow()
  .layout('/dashboard/*', ({ children, request }) => (
    <html lang='en' className={getInitialThemeClass(request)} suppressHydrationWarning>
      <body>{children}</body>
    </html>
  ))
```

## Importing custom CSS

If your CSS file is not named **`global.css`** or **`style.css`** at the project root, import it normally in your server entry:

```tsx
import './globals.css'
```

Your custom properties override Holocron's defaults because Holocron puts its default tokens in a low-priority cascade layer. Normal unlayered app CSS wins regardless of stylesheet load order.

## Custom homepage

With docs nested in `docs/`, your `/` route stays free for a custom homepage. Holocron will not redirect `/` to the first doc page when a parent route already handles it.

```tsx
// server.tsx
import { Spiceflow } from 'spiceflow'
import { app as holocronApp } from '@holocron.so/vite/app'

export const app = new Spiceflow()
  .page('/', () => (
    <html lang='en'>
      <head><title>My Product</title></head>
      <body>
        <h1>Welcome to My Product</h1>
        <a href='/docs/getting-started'>Read the docs</a>
      </body>
    </html>
  ))
  .get('/api/hello', () => ({ hello: 'world' }))
  .use(holocronApp)

export default {
  fetch(request: Request) {
    return app.handle(request)
  },
}
```

## Real-world example

The [holocron.so website](https://github.com/remorses/holocron/tree/main/website) uses this pattern. It mounts holocron docs alongside auth routes (better-auth with Google login), an AI gateway proxy, and a device authorization flow.


---
title: Spiceflow
url: "https://holocron.so/docs/spiceflow.md"
description: "How Holocron uses Spiceflow under the hood and how to extend your docs with API routes, middleware, and auth."
---

# Spiceflow

[Spiceflow](https://github.com/remorses/spiceflow) is a type-safe API and React Server Components framework for TypeScript. Holocron uses it as its web framework under the hood.

When you add `holocron()` to your Vite config, the plugin automatically adds the Spiceflow Vite plugin, React, and Tailwind CSS. You don't configure any of these separately. Holocron creates a Spiceflow app internally that registers routes for every page in your `docs.json` navigation.

```diagram
vite.config.ts
     │
     v
holocron() plugin
     │
     ├── spiceflow() vite plugin (auto-added)
     ├── @vitejs/plugin-react (auto-added)
     ├── @tailwindcss/vite (auto-added)
     │
     └── creates Spiceflow app internally
            │
            ├── page routes from docs.json
            ├── layout with sidebar, navbar, search
            ├── MDX rendering pipeline
            └── static assets (CSS, icons, fonts)
```

For most documentation sites, you never interact with Spiceflow directly. The `holocron()` plugin handles everything.

## When you need Spiceflow

Sometimes a docs site needs more than static pages. You might want to:

* Add **API routes** for webhooks, health checks, or data endpoints
* Add **middleware** for auth, logging, or custom headers on every request
* Add **redirects** that can't be expressed in `docs.json`
* Serve **custom pages** like a dashboard or admin panel alongside docs
* Mount holocron under a **sub-path** of a larger application

In these cases, you create a **custom entry** file that gives you a full Spiceflow app. Holocron becomes a child app you mount with `.use()`.

## Custom entry setup

Pass `entry` to the holocron plugin:

```ts
// vite.config.ts
import { defineConfig } from 'vite'
import { holocron } from '@holocron.so/vite'

export default defineConfig({
  plugins: [
    holocron({ entry: './src/server.tsx' }),
  ],
})
```

Then create your server file. Import the holocron app and mount it last so your own routes take priority:

```tsx
// src/server.tsx
import { Spiceflow } from 'spiceflow'
import { app as holocronApp } from '@holocron.so/vite/app'

export const app = new Spiceflow()
  // API routes
  .get('/api/health', () => ({ status: 'ok' }))
  .post('/api/webhook', async ({ request }) => {
    const body = await request.json()
    // process webhook...
    return { received: true }
  })
  // mount holocron last — it handles all docs pages
  .use(holocronApp)
```

```diagram
Request ──> your middleware ──> your routes ──> holocronApp
                                                   │
                                           docs pages, search,
                                           sidebar, MDX rendering
```

Your routes are checked first. If none match, the request falls through to holocron which serves the docs pages, search API, and static assets.

## Middleware

Middleware registered before `.use(holocronApp)` runs on **every request**, including docs pages. This is useful for auth, analytics, or headers:

```tsx
export const app = new Spiceflow()
  .use(async ({ request }, next) => {
    console.log(request.method, new URL(request.url).pathname)
    const res = await next()
    if (res) res.headers.set('x-docs-version', '2.0')
    return res
  })
  .use(holocronApp)
```

For auth-gated docs, check the session before holocron handles the request:

```tsx
export const app = new Spiceflow()
  .use(async ({ request }, next) => {
    const url = new URL(request.url)
    // public pages
    if (url.pathname === '/' || url.pathname.startsWith('/api/')) {
      return next()
    }
    // check auth for all other pages
    const session = await getSession(request)
    if (!session) {
      return Response.redirect(new URL('/login', url.origin).href)
    }
    return next()
  })
  .use(holocronApp)
```

## Serving extra files

A common pattern is serving an `llms.txt` file for AI agents or adding redirects that need custom logic:

```tsx
import readmeRaw from '../README.md?raw'

export const app = new Spiceflow()
  .get('/llms.txt', () => {
    return new Response(readmeRaw, {
      headers: { 'Content-Type': 'text/plain' },
    })
  })
  .get('/gh', ({ request }) => {
    return Response.redirect('https://github.com/example/repo', 302)
  })
  .use(holocronApp)
```

## Cloudflare Workers

When deploying to Cloudflare Workers, your entry file needs a `default export` with a `fetch` handler:

```tsx
import { Spiceflow } from 'spiceflow'
import { app as holocronApp } from '@holocron.so/vite/app'

export const app = new Spiceflow()
  .get('/api/hello', () => ({ hello: 'world' }))
  .use(holocronApp)

export default {
  async fetch(request: Request): Promise<Response> {
    return app.handle(request)
  },
}
```

Add the Cloudflare Vite plugin after holocron in your config:

```ts
import { cloudflare } from '@cloudflare/vite-plugin'
import { holocron } from '@holocron.so/vite'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    holocron({ entry: './src/server.tsx' }),
    cloudflare({
      viteEnvironment: {
        name: 'rsc',
        childEnvironments: ['ssr'],
      },
    }),
  ],
})
```

See [Cloudflare Workers deployment](/docs/deploy/cloudflare) for `wrangler.jsonc` setup and build commands.

## Real-world examples

* The [holocron.so website](https://github.com/remorses/holocron/tree/main/website) uses a custom entry with auth routes (better-auth), an AI gateway proxy, and device authorization flow alongside the docs.
* The [spiceflow website](https://github.com/remorses/spiceflow/tree/main/website) uses a custom entry to serve `/llms.txt` and `/gh` redirect alongside holocron docs, deployed to Cloudflare Workers.


---
title: OpenAPI Setup
url: "https://holocron.so/docs/api-docs/openapi.md"
description: Generate API reference pages from an OpenAPI spec.
---

# OpenAPI Setup

Holocron can generate API reference pages from an OpenAPI specification file. Point a tab at your spec and Holocron creates a page per endpoint, grouped by tag.

## Basic setup

Add an OpenAPI tab to your `docs.json` navigation:

```json
{
  "navigation": {
    "tabs": [
      {
        "tab": "Documentation",
        "groups": [
          { "group": "Getting Started", "pages": ["index"] }
        ]
      },
      {
        "tab": "API Reference",
        "openapi": "openapi.json"
      }
    ]
  }
}
```

Place your `openapi.json` (or `openapi.yaml`) file at the project root. If you set `pagesDir`, Holocron checks `pagesDir` first, then falls back to the project root.

## Multiple specs

Pass an array to combine multiple spec files:

```json
{
  "tab": "API Reference",
  "openapi": ["openapi/v1.json", "openapi/v2.json"]
}
```

## Custom base path

By default, generated pages appear under `/api/` (e.g. `/api/get-users`). Change the prefix with `base`:

```json
{
  "tab": "API Reference",
  "openapi": "openapi.json",
  "base": "/reference"
}
```

Now endpoints render at `/reference/get-users` instead.

A **leading slash is optional**: `"/reference"` and `"reference"` behave the same. Set `base` to `""` for no prefix.

<Aside>
  <Tip>
    When mounting docs inside your own app via [custom entry](/docs/custom-entry), set `base` to a `docs/`-prefixed slug like `/docs/api` so generated endpoints stay under `/docs/*` and don't collide with your real `/api` routes.
  </Tip>
</Aside>

## Mixing guides with endpoint pages

The basic setup above is **dedicated mode**: every endpoint is auto-grouped by tag. If you instead want to interleave hand-written pages (authentication, getting your API key, an overview) with specific endpoints, add a `groups` or `pages` array to the tab. This is **selective mode**.

In selective mode, each page entry is one of:

* a normal MDX slug like `guide/authentication` → renders that MDX file
* an endpoint reference like `POST /users` → renders the auto-generated endpoint page from the spec

```json
{
  "tab": "API Reference",
  "openapi": "openapi.json",
  "groups": [
    {
      "group": "Getting Started",
      "pages": [
        "api/overview",
        "api/authentication",
        "POST /auth/login",
        "GET /users"
      ]
    },
    {
      "group": "Orders",
      "pages": ["api/orders-intro", "POST /orders"]
    }
  ]
}
```

The sidebar follows the exact order you write, so your authentication guide can sit right before the endpoints it explains:

```diagram
API Reference
├─ Getting Started
│   ├─ Overview              ← api/overview.mdx
│   ├─ Authentication        ← api/authentication.mdx
│   ├─ POST /auth/login      ← generated endpoint page
│   └─ GET /users            ← generated endpoint page
└─ Orders
    ├─ Working with Orders   ← api/orders-intro.mdx
    └─ POST /orders          ← generated endpoint page
```

An endpoint reference matches the form `METHOD /path` (case-insensitive method). To pick a specific spec when you list several, prefix it with the file name: `"v2.json POST /orders"`.

If an endpoint reference does not match any operation in the spec, the build fails with an error so typos surface immediately.

### Intro page, then all endpoints

Listing every endpoint by hand is tedious. Add the special `"..."` entry to write a short intro and then auto-include all the remaining endpoints, grouped by tag, right after it:

```json
{
  "tab": "API Reference",
  "openapi": "openapi.json",
  "pages": ["api/authentication", "..."]
}
```

This renders your `api/authentication` guide first, then expands every endpoint not listed elsewhere into **top-level tag groups** (always-visible sidebar sections, exactly like dedicated mode):

```diagram
API Reference
├─ Authentication        ← api/authentication.mdx
├─ Users                 ← top-level group from "..." (tag: users)
│   ├─ GET  /users
│   └─ POST /users
└─ Orders                ← top-level group from "..." (tag: orders)
    └─ POST /orders
```

The tag groups are hoisted to the tab's top level so they render as separate sidebar sections, not collapsed sub-groups nested under your intro pages.

The `"..."` entry only expands endpoints you did **not** already reference explicitly, so you can pin a few important endpoints up top and let the rest fall in afterward. Only one `"..."` is allowed per tab.

## Customize generated pages

Add **`x-holocron`** to an OpenAPI operation to override its generated page metadata, insert MDX content, or choose its URL.

```yaml
paths:
  /users:
    post:
      summary: Create user
      x-holocron:
        metadata:
          title: Create a new user
          sidebarTitle: Create user
          description: Add a user to the current organization.
        content: |
          <Badge color="blue">1 Credit</Badge>

          <Note>
          User email addresses must be unique.
          </Note>
        href: /api-reference/users/create
```

The **`metadata`** object supports `title`, `sidebarTitle`, and `description`. The `content` field accepts Markdown and Holocron MDX components. Holocron renders it before the generated endpoint header, description, authorization, parameters, and request fields.

The optional **`href`** must be an internal path without a query string or hash. It replaces the normal method-and-path slug and does not use the tab's `base` prefix.

<Aside>
  <Info>
    Use **`x-holocron`** for new specifications. It is the canonical extension and can gain Holocron-specific options in future releases.
  </Info>
</Aside>

### Mintlify compatibility

Holocron treats **`x-mint` as a compatibility alias** for the same page overrides. Existing Mintlify specifications work without renaming the extension.

```yaml
paths:
  /users:
    post:
      x-mint:
        metadata:
          title: Create a new user
          sidebarTitle: Create user
        content: '<Badge color="blue">1 Credit</Badge>'
```

Some existing specifications put `title`, `sidebarTitle`, and `description` **directly inside the extension**. Holocron supports this legacy shape too. When direct fields and `metadata` both define a value, `metadata` wins.

If an operation defines **both extensions**, `x-holocron` wins field by field. Missing or invalid `x-holocron` fields fall back to valid `x-mint` values.

## Controlling which routes appear in the spec

Holocron documents every operation in your OpenAPI spec. If your API has **internal routes** (admin endpoints, webhook receivers, internal service calls) that should not appear in the public docs, exclude them from the spec itself rather than trying to filter them in `docs.json`.

Most frameworks let you mark routes as hidden at the route definition level so they are omitted from the generated spec.

### Spiceflow

Add `hide: true` to the route's `detail` object:

```ts
import { Spiceflow } from 'spiceflow'
import { openapi } from 'spiceflow/openapi'
import { z } from 'zod'

const app = new Spiceflow()
  .use(openapi({ path: '/openapi.json' }))

  // This route appears in the spec
  .route({
    method: 'GET',
    path: '/api/v0/projects',
    detail: {
      summary: 'List projects',
      tags: ['Projects'],
    },
    response: { 200: z.object({ projects: z.array(z.string()) }) },
    async handler() {
      return { projects: [] }
    },
  })

  // This route is hidden from the spec
  .route({
    method: 'POST',
    path: '/api/v0/keys',
    detail: {
      hide: true,
      summary: 'Create API key',
      tags: ['Internal'],
    },
    request: z.object({ name: z.string() }),
    async handler() {
      return { id: '123' }
    },
  })
```

<Aside>
  <Tip>
    A good rule of thumb: if a route requires **session auth** or **internal tokens** that your API consumers never have, hide it from the spec. Only document routes that work with the auth method your users actually use (e.g. API keys).
  </Tip>
</Aside>

### Other frameworks

Most OpenAPI generators support similar exclusion mechanisms:

* **Hono** (`@hono/zod-openapi`): omit the route from the OpenAPI app, or don't register it with `createRoute`
* **FastAPI**: set `include_in_schema=False` on the route decorator
* **Express** (`swagger-jsdoc`): omit the JSDoc `@openapi` annotation from the route

The key idea is the same: your OpenAPI spec should only contain routes your API consumers can actually call. Internal, admin, or webhook routes belong in a separate spec or no spec at all.

## How pages are generated

In **dedicated mode**, Holocron reads the spec and creates one page per operation (path + method). Pages are grouped by the `tags` field on each operation. The sidebar shows tag groups with individual endpoint pages.

Each generated page shows:

* HTTP method and path
* Description from the spec
* Request parameters, headers, and body schema
* Response schemas with status codes


---
title: Generated Pages
url: "https://holocron.so/docs/api-docs/generated-pages.md"
description: How OpenAPI operations become sidebar pages.
---

# Generated Pages

When you set `"openapi": "openapi.json"` on a tab, Holocron generates one page per operation in the spec.

## Slug format

Each operation gets a slug derived from the method and path:

| Operation     | Generated slug   |
| ------------- | ---------------- |
| `GET /users`  | `api/get-users`  |
| `POST /users` | `api/post-users` |

The `base` prefix (default `"api"`) is prepended to all slugs.

### Custom endpoint URLs

Set **`x-holocron.href`** on an operation to replace its generated slug. The Mintlify-compatible `x-mint.href` alias works too.

```yaml
paths:
  /users:
    get:
      x-holocron:
        href: /api-reference/users/list
```

This endpoint renders at **`/api-reference/users/list`** instead of `/api/get-users`. Custom `href` values must be internal paths without a query string or hash.

## Grouping by tags

Operations are grouped by their `tags` field in the spec. Each tag becomes a sidebar group:

```yaml
paths:
  /users:
    get:
      tags: [Users]
      operationId: listUsers
    post:
      tags: [Users]
      operationId: createUser
  /teams:
    get:
      tags: [Teams]
      operationId: listTeams
```

This produces:

* **Users** group: `get-users`, `post-users`
* **Teams** group: `get-teams`

## Page layout

Generated API pages use a two-column layout:

* **Left column**: description, parameters, request body schema
* **Right column**: request and response examples (displayed in the aside)


---
title: Request and Response Examples
url: "https://holocron.so/docs/api-docs/request-response-examples.md"
description: How request and response schemas are displayed on API pages.
---

# Request and Response Examples

Generated OpenAPI pages render request and response information based on your spec's schemas.

## Request parameters

Query parameters, path parameters, and headers are listed with their type, description, and required/optional status.

## Request body

If an operation has a `requestBody`, Holocron renders the schema as a structured field list. JSON Schema properties become rows with name, type, and description.

## Response schemas

Each response status code (200, 201, 400, etc.) is rendered with its schema. Multiple response codes are shown in separate sections.

## Examples in the spec

Holocron renders a generated **cURL** request in the right-side aside, plus:

* **`x-codeSamples`** on the operation (SDK / CLI snippets) as extra Request example tabs. See [SDK examples](/docs/api-docs/sdk-examples).
* Named request-body and response **`example` / `examples`** as JSON tabs.

Schema and parameter examples are still useful context in the field list, but they are not separate aside code blocks.

```yaml
paths:
  /users:
    get:
      responses:
        '200':
          description: List of users
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'
              example:
                - id: 1
                  name: Alice
                - id: 2
                  name: Bob
```

## Two-column layout

On desktop, the request/response examples appear in the right aside column next to the parameter descriptions. On mobile, they stack below the parameters.


---
title: SDK code samples in OpenAPI
url: "https://holocron.so/docs/api-docs/sdk-examples.md"
description: "Show TypeScript, Python, and other SDK snippets on API pages with x-codeSamples."
---

# SDK code samples in OpenAPI

If users call your API through an **SDK** instead of raw HTTP, add samples with the OpenAPI extension **`x-codeSamples`**. Holocron shows them as extra tabs in the **Request example** panel next to the generated cURL snippet.

This is the same extension [Mintlify](https://www.mintlify.com/docs/api-playground/adding-sdk-examples), Stainless, Speakeasy, and hey-api use.

## Where samples appear

On each generated endpoint page, the right aside **Request example** panel lists tabs in this order:

1. **cURL** (always generated)
2. Each **`x-codeSamples`** entry (your SDK snippets)
3. Named **request body** `examples` from the spec (JSON payloads)

```diagram
  OpenAPI operation
        │
        v
  ┌─────────────────────────────────────────┐
  │ Request example                         │
  │  cURL  │  TypeScript  │  Python  │  …   │
  │  ┌───────────────────────────────────┐  │
  │  │  await client.users.list()        │  │
  │  └───────────────────────────────────┘  │
  └─────────────────────────────────────────┘
```

SDK tabs are **copy-only**. Live try-it still uses HTTP.

Language tabs **sync by title** across Request example panels (and other
`<Tabs sync>` groups). Pick TypeScript once and the next endpoint opens on
TypeScript when that tab exists.

## Hand-written samples

Add `x-codeSamples` on any path method:

```yaml
paths:
  /users:
    get:
      summary: List users
      x-codeSamples:
        - lang: TypeScript
          label: TypeScript
          source: |
            import { Acme } from '@acme/sdk'
            const client = new Acme()
            await client.users.list({ role: 'editor' })
        - lang: Python
          label: Python
          source: |
            from acme import Acme
            client = Acme()
            client.users.list(role="editor")
        - lang: bash
          label: CLI
          source: |
            acme users list --role editor
```

| Field        | Required | Description                                                                                           |
| ------------ | -------- | ----------------------------------------------------------------------------------------------------- |
| **`lang`**   | yes      | Language id for highlighting (`TypeScript`, `python`, `bash`, …)                                      |
| **`source`** | yes      | Inline snippet body (string). Use a string, not a `$ref`; external refs are not resolved for samples. |
| **`label`**  | no       | Tab title. Defaults to `lang`.                                                                        |

Multiple samples can share a language with different labels (for example two TypeScript flows).

<Aside>
  <Tip>
    Keep **`label`** short. The Request example tab bar is narrow; long labels force horizontal scroll.
  </Tip>
</Aside>

## Stainless and Speakeasy

Point Holocron at a **decorated** OpenAPI file (or URL once remote specs are supported) that already includes `x-codeSamples`.

**Stainless:** set `openapi.code_samples: 'mintlify'` in `stainless.yml`, publish the OpenAPI URL from the Release tab, and set that file or path as your tab `openapi` value.

**Speakeasy:** use the registry **combined spec** entry (`*-with-code-samples`) and point `openapi` at that document.

No Holocron-specific config beyond loading the decorated spec.

## Generate samples with hey-api

[hey-api](https://heyapi.dev) (`@hey-api/openapi-ts`) can generate a typed SDK and **write `x-codeSamples` back into a source OpenAPI JSON**. Point Holocron at that decorated file.

### 1. Install

```bash
npm install -D @hey-api/openapi-ts
```

### 2. Config

```ts
// openapi-ts.config.ts
import { defineConfig } from '@hey-api/openapi-ts'

export default defineConfig({
  input: './openapi.yaml',
  output: {
    path: './src/client',
    // Writes a decorated copy of the input spec after intents run.
    // Path is resolved as path.resolve(output.path, source.path, fileName + '.json')
    source: {
      path: '../..', // project root when output is ./src/client
      fileName: 'openapi.with-samples',
    },
  },
  plugins: [
    '@hey-api/client-fetch',
    {
      name: '@hey-api/sdk',
      // Generate TypeScript usage snippets and attach them via x-codeSamples
      examples: {
        language: 'TypeScript',
      },
    },
  ],
})
```

`output.source` writes the **input** document after intents run. The SDK plugin’s `examples` option appends each generated snippet to that operation’s `x-codeSamples`.

The file lands at `path.resolve(output.path, source.path, fileName + '.json')`. With the config above that is **`openapi.with-samples.json`** at the project root. If `source.path` is `'..'` instead, the file would be `src/openapi.with-samples.json`.

### 3. Script

```json
// package.json
{
  "scripts": {
    "openapi:sdk": "openapi-ts"
  }
}
```

```bash
npm run openapi:sdk
```

### 4. Point Holocron at the decorated file

```json
{
  "tab": "API Reference",
  "openapi": "openapi.with-samples.json"
}
```

Regenerate the decorated file in CI whenever the base OpenAPI or SDK config changes, then build docs.

```diagram
  openapi.yaml ──> openapi-ts (sdk.examples) ──> openapi.with-samples.json
                                                         │
                                                         v
                                              docs.json openapi field
                                                         │
                                                         v
                                              Request example tabs
```

<Aside>
  <Note>
    You can also hand-edit `x-codeSamples` on a few important endpoints and leave the rest as cURL only. Mixed specs work fine.
  </Note>
</Aside>

## Example in this monorepo

The Holocron **example** site ships `example/api.yaml` with `x-codeSamples` on **List users** and **Create user**. Open the API Reference tab locally and switch the Request example tabs between cURL and the SDK languages.

## Related

* [OpenAPI setup](/docs/api-docs/openapi)
* [Request and response examples](/docs/api-docs/request-response-examples)
* [Generated pages](/docs/api-docs/generated-pages)


---
title: OpenAPI Troubleshooting
url: "https://holocron.so/docs/api-docs/troubleshooting.md"
description: Common issues with OpenAPI spec rendering.
---

# OpenAPI Troubleshooting

## Spec not found

If Holocron cannot find your spec file, the dev server or build fails and prints the paths it checked. Check:

* The path in `docs.json` is relative to `pagesDir` first, then the project root
* The file extension matches (`.json` or `.yaml`)
* The file exists and is valid JSON/YAML

## No pages generated

If the API Reference tab is empty:

* Make sure your spec has `paths` with at least one operation
* Untagged operations are grouped under **Default**, so missing tags do not prevent page generation

## Schema rendering issues

* **References**: Holocron dereferences `$ref` values and limits deeply nested schema expansion to keep pages readable
* **`allOf`/`oneOf`/`anyOf`**: Union fields are preserved and rendered in the schema view
* **Missing descriptions**: Operations without a `summary` or `description` show the operationId as the title

## Wrong URL prefix

If pages appear at `/api/...` but you want a different prefix, set `base` on the tab:

```json
{
  "tab": "API Reference",
  "openapi": "openapi.json",
  "base": "reference"
}
```

Set to `""` for no prefix at all.


---
title: Changelog Tab
url: "https://holocron.so/docs/changelog-tab.md"
description: "Generate a changelog page from a GitHub repository's releases."
---

# Changelog Tab

Holocron can generate a **changelog page** from a GitHub repository's releases. Point a tab at the repository URL and Holocron fetches the published releases, rendering one entry per release.

## Basic setup

Add a changelog tab to your `docs.json` navigation. The `changelog` field is the full URL of the repository:

```json
{
  "navigation": {
    "tabs": [
      {
        "tab": "Documentation",
        "groups": [{ "group": "Getting Started", "pages": ["index"] }]
      },
      {
        "tab": "Changelog",
        "changelog": "https://github.com/owner/repo"
      }
    ]
  }
}
```

The generated page is served at `/changelog`. Each release becomes an entry showing its tag, publish date, and release notes (rendered as Markdown). Prereleases are included; draft releases are skipped.

## Page layout

The changelog page hides the **left navigation sidebar** so the release notes get the full content width. A notice in the right column explains the page is generated from the GitHub releases page.

## Custom slug

By default the page is served at `/changelog`. Change it with `base`:

```json
{
  "tab": "Releases",
  "changelog": "https://github.com/owner/repo",
  "base": "/releases"
}
```

Now the page is served at `/releases`. A **leading slash is optional**: `"/releases"` and `"releases"` behave the same.

## Custom intro content

Use `initialContent` to prepend custom MDX content above the release entries. Point it at an MDX file (resolved from your pages directory):

```json
{
  "tab": "Changelog",
  "changelog": "https://github.com/owner/repo",
  "initialContent": "changelog/intro"
}
```

The referenced file's body (everything after the frontmatter) is spliced into the top of the generated changelog page. This is useful for adding an `<Above>` hero section, an introduction paragraph, or any custom component before the release entries start.

## Private repositories

Holocron supports **private GitHub repositories** out of the box. At build time, it tries the [GitHub CLI](https://cli.github.com/) (`gh`) first, then falls back to a direct HTTP request.

### Using the `gh` CLI (recommended)

If the `gh` CLI is installed and you've run `gh auth login`, Holocron calls `gh api` under the hood. This picks up your stored credentials automatically, so private repos just work with no extra configuration.

```bash
# One-time setup
gh auth login
```

### Using an environment variable

If `gh` is not available (Docker builds, minimal CI images), set a **`GITHUB_TOKEN`** or **`GH_TOKEN`** environment variable with read access to the repository. Holocron sends it as a bearer token.

```bash
GITHUB_TOKEN=ghp_xxxx npx vite build
```

### GitHub Actions

GitHub Actions comes with `gh` pre-installed and a default `GITHUB_TOKEN`. To make it available during the build, pass it as an environment variable in your deploy step:

```yaml
- name: Deploy
  run: bunx holocron deploy
  env:
    HOLOCRON_KEY: ${{ secrets.HOLOCRON_KEY }}
    GITHUB_TOKEN: ${{ github.token }}
```

The default Actions token has read access to the **current repository**. If your changelog points to a different private repo in the same organization, use a [fine-grained personal access token](https://github.com/settings/tokens?type=beta) with `contents: read` scope on the target repo, stored as a repository secret.

## Rate limits

Unauthenticated requests to the GitHub releases API are limited to **60 per hour**. Authenticating via `gh` or a token raises this to 5,000 per hour. If you see a "Could not load releases" warning during builds, rate limiting is the most likely cause.


---
title: MCP Docs
url: "https://holocron.so/docs/mcp-tools.md"
description: Generate documentation pages from MCP tool and resource definitions.
---

# MCP Docs

Holocron generates documentation pages from [MCP](https://modelcontextprotocol.io) (Model Context Protocol) tool and resource definitions. Point a tab at a **local definition file** or a **remote MCP server URL** and Holocron creates a page for each tool and resource, with parameter docs, example requests, and annotation badges.

## Basic setup

Add an MCP tab to your `docs.json` navigation. The `mcp` field accepts either a **local JSON file path** or a **remote MCP server URL**:

```json
{
  "navigation": {
    "tabs": [
      {
        "tab": "Documentation",
        "groups": [{ "group": "Getting Started", "pages": ["index"] }]
      },
      {
        "tab": "MCP Tools",
        "mcp": "mcp-tools.json"
      }
    ]
  }
}
```

Or connect directly to a live MCP server:

```json
{
  "tab": "MCP Tools",
  "mcp": "https://api.example.com/mcp"
}
```

When using a remote URL, Holocron connects via [Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) at build time, calls `tools/list`, `resources/list`, and `prompts/list`, and generates the same pages as the local file approach.

For local files, place the JSON at the project root. If you set `pagesDir`, Holocron checks `pagesDir` first, then falls back to the project root.

### Exporting from an existing MCP server

If you already have a running MCP server, you can generate the definition file with a simple script. See [Exporting MCP definitions](/docs/mcp-export) for a ready-to-use TypeScript script.

## Definition file format

The local file uses the **exact same shape** returned by the MCP SDK's `tools/list`, `resources/list`, and `prompts/list` responses. If you already have an MCP server, you can export its definitions directly. The file is a JSON object with three optional arrays:

```json
{
  "serverUrl": "https://api.example.com/mcp",
  "tools": [
    {
      "name": "query_database",
      "description": "Execute a read-only SQL query.",
      "inputSchema": {
        "type": "object",
        "properties": {
          "query": {
            "type": "string",
            "description": "SQL query to execute",
            "example": "SELECT * FROM users LIMIT 10"
          },
          "database": {
            "type": "string",
            "enum": ["production", "staging"]
          }
        },
        "required": ["query", "database"]
      }
    }
  ],
  "resources": [
    {
      "uri": "db://schema/users",
      "name": "Users Table Schema",
      "description": "Database schema for the users table.",
      "mimeType": "application/sql"
    }
  ],
  "prompts": [
    {
      "name": "explain_query",
      "description": "Break down what a SQL query does",
      "arguments": [
        { "name": "query", "description": "The SQL query to explain", "required": true }
      ]
    }
  ]
}
```

The `serverUrl` field is optional. It stores the live MCP server URL for future AI chat integration.

### Tool fields

Each tool object supports these fields:

<ResponseField name="name" type="string" required>
  Unique identifier for the tool. Used as the page slug (converted to kebab-case).
</ResponseField>

<ResponseField name="description" type="string">
  Human-readable description. Rendered as Markdown on the tool page.
</ResponseField>

<ResponseField name="inputSchema" type="object" required>
  JSON Schema defining the tool's parameters. Each property becomes a documented field with type, description, default, and enum values.
</ResponseField>

<ResponseField name="outputSchema" type="object">
  JSON Schema defining the tool's return value. When present, a **Response** section and a response example appear on the page.
</ResponseField>

<ResponseField name="annotations" type="object">
  Behavioral hints rendered as colored badges on the tool page. See [annotations](#annotations) below.
</ResponseField>

<ResponseField name="execution" type="object">
  Execution hints. When `taskSupport` is `"optional"` or `"required"`, a **long-running** badge appears.
</ResponseField>

### Resource fields

<ResponseField name="uri" type="string" required>
  The resource URI (e.g. `db://schema/users`, `config://app/settings`).
</ResponseField>

<ResponseField name="name" type="string" required>
  Display name for the resource.
</ResponseField>

<ResponseField name="description" type="string">
  Human-readable description rendered as Markdown.
</ResponseField>

<ResponseField name="mimeType" type="string">
  MIME type badge shown next to the URI (e.g. `application/json`, `application/sql`).
</ResponseField>

## What gets generated

**Tool pages** show the tool name with a `TOOL` badge, the description, input parameters as a field list (with types, required markers, defaults, and enum values), and a request example in the right sidebar showing the JSON-RPC `tools/call` shape with sampled values from the schema.

When a tool defines `outputSchema`, a **Response** field list and a `<ResponseExample>` code block also appear.

**Resource pages** show the resource name with a `RES` badge, the URI, MIME type, and description.

Tools and resources are auto-grouped into **Tools** and **Resources** sidebar groups. Pages appear under `/mcp/` by default (e.g. `/mcp/query-database`, `/mcp/resources/users-table-schema`).

### Example input generation

Holocron generates realistic example values from the JSON Schema using this priority:

1. `example` field on the property
2. `examples[0]`
3. `enum[0]`
4. `default` value
5. Type-based fallback (`"string"`, `0`, `true`, `"user@example.com"` for `format: "email"`, etc.)

Add `example` values to your `inputSchema` properties for the best documentation experience.

## Annotations

MCP tool annotations are behavioral hints rendered as colored badges with tooltips:

| Badge            | Annotation                                        | Meaning                                                                  |
| ---------------- | ------------------------------------------------- | ------------------------------------------------------------------------ |
| **read-only**    | `readOnlyHint: true`                              | Tool does not modify its environment                                     |
| **idempotent**   | `idempotentHint: true`                            | Repeated calls with same args have no additional effect                  |
| **destructive**  | `destructiveHint: true`                           | Tool may perform destructive updates                                     |
| **closed-world** | `openWorldHint: false`                            | Tool operates in a closed domain (e.g. memory), not open like web search |
| **long-running** | `execution.taskSupport: "optional" \| "required"` | Tool supports long-running async tasks                                   |

Hover over any badge to see its description.

## Custom base path

By default, generated pages appear under `/mcp/`. Change the prefix with `base`:

```json
{
  "tab": "MCP Tools",
  "mcp": "mcp-tools.json",
  "base": "/tools"
}
```

Now tool pages render at `/tools/query-database` instead. A **leading slash is optional**: `"/tools"` and `"tools"` behave the same. Set `base` to `""` for no prefix.

## Mixing guides with tool pages

Like OpenAPI, you can interleave hand-written MDX pages with auto-generated tool pages using **selective mode**. Add a `groups` or `pages` array to the tab:

```json
{
  "tab": "MCP Tools",
  "mcp": "mcp-tools.json",
  "groups": [
    {
      "group": "Getting Started",
      "pages": ["mcp/overview", "mcp/authentication"]
    },
    {
      "group": "Database",
      "pages": ["query_database", "..."]
    }
  ]
}
```

Each page entry is either a normal MDX slug or a **tool name** (matched against the definition file). The special `"..."` entry expands all remaining tools and resources not already listed.

## HMR in development

During `npx vite dev`, editing the MCP definition file **hot-reloads** the generated tool pages. Add a new tool to the JSON, save, and the new page is immediately routable without restarting the dev server.


---
title: Exporting MCP Definitions
url: "https://holocron.so/docs/mcp-export.md"
description: Download tool and resource definitions from a running MCP server into a JSON file.
---

# Exporting MCP Definitions

If you have a running MCP server and want to generate a local definition file for Holocron, use this script. It connects to the server, fetches all tools, resources, and prompts, and writes them to a JSON file.

## Script

Save this as `scripts/export-mcp.ts` in your project:

```ts
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
import fs from 'node:fs'

const url = process.argv[2]
if (!url) {
  console.error('Usage: npx tsx scripts/export-mcp.ts <MCP_SERVER_URL>')
  process.exit(1)
}

const client = new Client({ name: 'holocron-export', version: '1.0.0' })
const transport = new StreamableHTTPClientTransport(new URL(url))

await client.connect(transport)
console.log('Connected to', url)

const [toolsResult, resourcesResult, promptsResult] = await Promise.all([
  client.listTools().catch(() => ({ tools: [] })),
  client.listResources().catch(() => ({ resources: [] })),
  client.listPrompts().catch(() => ({ prompts: [] })),
])

const output = {
  serverUrl: url,
  tools: toolsResult.tools ?? [],
  resources: resourcesResult.resources ?? [],
  prompts: promptsResult.prompts ?? [],
}

await client.close()

const outPath = 'mcp-tools.json'
fs.writeFileSync(outPath, JSON.stringify(output, null, 2) + '\n')
console.log(`Wrote ${output.tools.length} tools, ${output.resources.length} resources, ${output.prompts.length} prompts to ${outPath}`)
```

## Usage

Install the MCP SDK, then run the script with your server URL:

```bash
npm install @modelcontextprotocol/sdk
npx tsx scripts/export-mcp.ts https://your-mcp-server.com/mcp
```

This creates `mcp-tools.json` at the project root, ready to use in your `docs.json`:

```json
{
  "tab": "MCP Tools",
  "mcp": "mcp-tools.json"
}
```

Re-run the script whenever your MCP server's tools change and commit the updated file.


---
title: Markdown Routes
url: "https://holocron.so/docs/ai/markdown-routes.md"
description: Every page has a .md route for AI agents and scrapers.
---

# Markdown Routes

Every docs page in Holocron is also available as agent-readable Markdown by appending `.md` to the URL.

## How it works

| URL              | Returns                            |
| ---------------- | ---------------------------------- |
| `/quickstart`    | HTML page                          |
| `/quickstart.md` | Transformed MDX source as Markdown |

This makes your docs machine-readable out of the box. AI agents, documentation scrapers, and CLI tools can fetch page source without parsing HTML. Holocron strips human-only `<Visibility>` blocks and prepends an agent directive to the response.

## AI agent redirect

Holocron detects AI agents by their User-Agent string and automatically redirects them from the HTML page to the `.md` version with a 302. This means agents requesting `/quickstart` get the Markdown content directly.

The redirect only applies to recognized agent User-Agent strings. Normal browsers always get the HTML page.

## Sitemap comments

The generated sitemap includes comments explaining that agents can append `.md` to page URLs and download `/docs.zip` for the full corpus.


---
title: llms.txt
url: "https://holocron.so/docs/ai/llms-txt.md"
description: Agent-readable docs index and full-content endpoint for AI agents.
---

# llms.txt

Holocron generates two agent-facing text files automatically:

* **`/llms.txt`** — lightweight index with page titles and `.md` URLs
* **`/llms-full.txt`** — full content of every page concatenated into one file

Both follow the [llms.txt spec](https://llmstxt.org/).

## /llms.txt

The index file lists the site name, description, a link to `/docs.zip` for bulk download, and every page with its title and `.md` URL. Agents read this to discover the site structure and then fetch individual pages.

```bash
curl https://your-docs-site.com/llms.txt
```

### Example output

````
# My Docs

> Documentation and usage guide for My Docs.

## Best way to inspect these docs

Download all docs as markdown files and grep them locally:

```bash
curl -L https://example.com/docs.zip -o docs.zip
unzip docs.zip -d docs
grep -R "search term" docs/
```

Use this when you need to search across the whole documentation set. The zip contains every page as a .md file.

## Page index

You can also fetch individual markdown pages directly:

- [Getting Started](https://example.com/getting-started.md)
- [Authentication](https://example.com/guides/auth.md)
- [Deployment](https://example.com/deploy.md)
````

## /llms-full.txt

The full-content file includes every page's markdown body in a single response. Pages appear in the same order as your `docs.json` navigation and are separated by frontmatter blocks containing the page title, URL, and description.

```bash
curl https://your-docs-site.com/llms-full.txt
```

This is useful for agents that want to ingest the entire documentation set in one request without downloading a zip or making per-page requests.

### Example output

```
# My Docs

> Documentation and usage guide for My Docs.

This file contains the full content of all documentation pages. For a compact index, see llms.txt. To download all pages as a zip, use docs.zip.

---
title: Getting Started
url: https://example.com/getting-started.md
description: Learn how to set up My Docs.
---

# Getting Started

Follow these steps to get started...

---
title: Authentication
url: https://example.com/guides/auth.md
---

# Authentication

Configure authentication for your API...
```

## Base path

Both routes are available at the root (`/llms.txt`, `/llms-full.txt`) and under your base path if configured (e.g. `/docs/llms.txt`, `/docs/llms-full.txt`).


---
title: docs.zip
url: "https://holocron.so/docs/ai/docs-zip.md"
description: Download the entire docs site as a zip of Markdown files.
---

# docs.zip

Holocron serves a `/docs.zip` endpoint that bundles every page as a Markdown file in a single zip archive.

## Usage

```bash
curl -L https://your-docs-site.com/docs.zip -o docs.zip
unzip docs.zip -d docs
```

This gives agents and developers a local copy of all documentation for offline search, grep, or RAG pipelines.

## When to use this

* **AI agents** that need to ingest the full docs corpus at once
* **Local grep** for finding patterns across all pages
* **Offline reading** or archival

## How it works

The zip is generated on-the-fly from the same transformed MDX source used by `.md` routes. Each file in the archive corresponds to a page slug (e.g. `quickstart.md`, `guides/auth.md`).


---
title: Skill Discovery
url: "https://holocron.so/docs/ai/skill-discovery.md"
description: Agent-skills spec and .well-known discovery for AI agents.
---

# Skill Discovery

Holocron implements the **agent-skills 0.2.0 discovery spec** (RFC 8615 extension) and a legacy format for backward compatibility. This lets AI agents discover your docs as a "skill" they can load.

## Discovery endpoints

| Endpoint                                    | Format              |
| ------------------------------------------- | ------------------- |
| `/.well-known/agent-skills/index.json`      | v0.2.0 JSON index   |
| `/.well-known/agent-skills/<name>/SKILL.md` | Skill markdown file |
| `/.well-known/skills/index.json`            | Legacy JSON index   |
| `/.well-known/skills/<name>/SKILL.md`       | Legacy skill file   |

The `<name>` is derived from your site's `name` field in `docs.json` (lowercased, kebab-cased).

## SKILL.md content

The generated `SKILL.md` includes:

* Frontmatter with `name` and `description` (from your config)
* Instructions for fetching the sitemap
* An example page `.md` route
* Instructions for downloading `/docs.zip` and grepping it locally

## How agents use this

1. Agent fetches `/.well-known/agent-skills/index.json`
2. Discovers available skills with names and descriptions
3. Fetches the `SKILL.md` for a relevant skill
4. Follows instructions inside (usually downloading `/docs.zip`)

## Customizing the description

The skill description comes from the `description` field in your `docs.json`:

```json
{
  "name": "My SDK",
  "description": "SDK for building widgets with the Acme API"
}
```

If no description is set, Holocron generates a default from the site name.


---
title: AI Assistant
url: "https://holocron.so/docs/ai/assistant.md"
description: Built-in chat assistant for your docs site.
---

# AI Assistant

Holocron includes a built-in AI chat assistant. Users can ask questions about your documentation and get answers based on the docs content. By default it appears as a **sidebar widget**. Set `assistant.display` to `floating` to show a bottom pill instead.

To embed the chat on an **external website** (outside your docs site), see the [Chat Widget](/docs/ai/chat-widget) page.

## Enabling and disabling

The assistant is enabled by default. To disable it:

```json
{
  "assistant": {
    "enabled": false
  }
}
```

When disabled, the sidebar widget, chat drawer, and mobile "Ask AI" button are hidden. The chat route still exists and returns `404 Assistant is disabled`.

## Display location

By default the assistant sits in the **right sidebar**. Set `assistant.display` to `floating` to use the same bottom pill as the [embeddable ChatWidget](/docs/ai/chat-widget) instead:

```json
{
  "assistant": {
    "display": "floating"
  }
}
```

| Value      | Trigger                                                      |
| ---------- | ------------------------------------------------------------ |
| `sidebar`  | Ask AI widget in the right aside (default)                   |
| `floating` | Bottom-center textarea pill that morphs into the chat drawer |

Floating mode hides the sidebar widget and the mobile "Ask AI" button. The pill is the trigger on every screen size.

[Compact layout](/docs/customize/layout) in `docs.json` defaults to **floating** unless you set `assistant.display` yourself. Compact removes the right aside, so the sidebar widget cannot show.

## Suggested prompts

The empty chat screen shows a short pitch and up to three **suggestion links**. Clicking a suggestion submits it as a prompt. Customize them with `assistant.suggestions`:

```json
{
  "assistant": {
    "suggestions": [
      "What is Acme?",
      "Show me the quickstart",
      "Search the docs for ..."
    ]
  }
}
```

A suggestion ending with `...` is treated as **open-ended**: clicking it fills the chat input with the text (dots removed) and focuses it, so the user can complete the query before sending.

When `suggestions` is omitted, three defaults based on the site name are shown:

* `What is {site name}?`
* `Guide me through the pages I should read first`
* `Search the docs for ...`

## Support email

Set **`assistant.supportEmail`** so the assistant can send people to a human when the docs are not enough:

```json
{
  "assistant": {
    "supportEmail": "support@example.com"
  }
}
```

When this is set, the chat system prompt tells the model to share that address if the user wants to **talk to a human**, or if the question cannot be answered from the docs. The assistant answers from the docs first and does not invent other support channels.

This address is also present in the page HTML, so use an inbox with spam filtering.

<Aside>
  <Tip>
    Use a real inbox that your team reads. The assistant will quote this address in chat replies.
  </Tip>
</Aside>

## How it works

The assistant uses the hosted Holocron AI gateway to process questions. When deployed, the docs app sends the current page plus either inline docs in local development or a `docs.zip` URL in production, then streams the answer back into the chat UI.

Hosted chat uses Cloudflare Workers AI. Authenticated sites send `HOLOCRON_KEY`; unauthenticated requests use a temporary fallback with stricter IP rate limits. The default model is GLM 4.7 Flash.

## Table of contents in the sidebar

With `display: "sidebar"` (the default), the AI assistant widget occupies the right sidebar. If you prefer a traditional **table of contents** instead (or alongside the widget), add the `TableOfContentsPanel` component to your page:

```mdx
<Aside full>
  <TableOfContentsPanel />
</Aside>
```

This renders a sticky "On this page" panel with heading links and active-state highlighting as the user scrolls. When the sidebar AI widget is enabled, it appears above the TOC panel automatically. With `display: "floating"` or `enabled: false`, the TOC panel takes the full sidebar.

See the [Table of Contents component page](/docs/components/table-of-contents) for all props and usage patterns.

## What gets disabled

Setting `enabled: false` removes:

* The sidebar chat widget
* The chat drawer overlay
* The mobile "Ask AI" floating button
* Useful responses from `/holocron-api/chat`; the endpoint returns 404 while disabled

## Skill loading

The hosted gateway can accept remote skill URLs, but the built-in docs assistant does not expose a `docs.json` setting for them yet. Today it answers from the current docs content and the docs zip payload.


---
title: Embed the AI Chat Widget on Any Website
url: "https://holocron.so/docs/ai/chat-widget.md"
description: Drop-in React component that adds a docs-aware AI chat to any website.
---

# Chat Widget

The `ChatWidget` component from `@holocron.so/vite/chat` adds a **docs-aware AI chat** to any React app. Point it at your holocron docs site and users get a floating textarea pill that opens into a full chat drawer, with streaming responses grounded in your documentation.

```tsx
import { ChatWidget } from '@holocron.so/vite/chat'

<ChatWidget
  domain="docs.myapp.com"
  navigate={(path) => router.push(path)}
/>
```

<Aside>
  <Note>
    This page covers the **standalone widget** for embedding on external sites. If you're looking for the built-in assistant that ships with every holocron docs site, see [AI Assistant](/docs/ai/assistant).
  </Note>
</Aside>

The widget renders inside a **Shadow DOM** for style isolation from the host page. Pill ↔ drawer morph uses **Motion `layoutId`** (CSS view transitions ignore elements inside shadow roots).

## Installation

Install the holocron vite package:

```bash
npm install @holocron.so/vite
```

Import and render the widget in your app. The `domain` prop must point to a running holocron docs site:

```tsx
import { ChatWidget } from '@holocron.so/vite/chat'
import { router } from 'your-framework/router'

function App() {
  return (
    <>
      {/* Your app content */}
      <ChatWidget
        domain="docs.myapp.com"
        siteName="My App"
        navigate={(path) => router.push(path)}
      />
    </>
  )
}
```

The widget fetches documentation from the site's `/holocron-api/chat` endpoint and streams responses back via RSC federation.

## Props

<ResponseField name="domain" type="string" required>
  Domain of the holocron docs site (e.g. `"docs.myapp.com"`). The widget connects to `https://{domain}/holocron-api/chat`.
</ResponseField>

<ResponseField name="navigate" type="(path: string) => void | Promise<void>" required>
  Client-side navigation function. Called by browser automation tools when the AI navigates the user to a page.

  * **Next.js**: `(path) => router.push(path)`
  * **React Router**: `(path) => navigate(path)`
  * **Spiceflow**: `(path) => router.push(path)`
</ResponseField>

<ResponseField name="trigger" type="React.ComponentType<{ onClick: () => void }>">
  Custom trigger component replacing the default textarea pill. Receives `onClick` to toggle the chat drawer.
</ResponseField>

<ResponseField name="siteName" type="string">
  Site name shown in the chat panel header. Defaults to empty string.
</ResponseField>

<ResponseField name="currentSlug" type="string">
  Current page slug for context (e.g. `"/quickstart"`). Tells the AI which page the user is on. Defaults to `"/"`.
</ResponseField>

<ResponseField name="theme" type="'light' | 'dark' | 'system'">
  Color theme for the widget.

  * `'light'` — always light mode
  * `'dark'` — always dark mode
  * `'system'` — follows OS `prefers-color-scheme` (default)
</ResponseField>

<ResponseField name="style" type="React.CSSProperties">
  CSS variable overrides applied on the widget container. Use this to customize colors.
</ResponseField>

<ResponseField name="className" type="string">
  Class name for the widget container element.
</ResponseField>

<ResponseField name="tools" type="ChatToolDefinition[]">
  Client-side tools that execute in the browser when the model calls them. See [Client-side Tools](#client-side-tools).
</ResponseField>

<ResponseField name="context" type="Record<string, unknown>">
  Arbitrary context object injected into the system prompt as XML. Use this to give the AI information about the current user, page, or app state.
</ResponseField>

## Theming

The widget uses **CSS custom properties** for all colors. The `theme` prop toggles between light and dark palettes:

```tsx
// Always dark widget
<ChatWidget domain="docs.myapp.com" theme="dark" navigate={navigate} />

// Follow system preference (default)
<ChatWidget domain="docs.myapp.com" theme="system" navigate={navigate} />
```

### CSS variable overrides

Override individual tokens via the `style` prop. The widget uses zero-specificity defaults (`:where(.holocron-chat)`), so your overrides always win:

```tsx
<ChatWidget
  domain="docs.myapp.com"
  navigate={navigate}
  style={{
    '--background': '#1a1a2e',
    '--foreground': '#eaeaea',
    '--primary': '#6366f1',
    '--border': '#2a2a4a',
  } as React.CSSProperties}
/>
```

<Aside>
  <Tip>
    Available tokens: `--background`, `--foreground`, `--muted`, `--muted-foreground`, `--accent`, `--card`, `--border`, `--border-subtle`, `--destructive`.
  </Tip>
</Aside>

### Pill size

Control the pill width with the `--pill-width` and `--pill-expand` CSS variables via the `style` prop:

```tsx
<ChatWidget
  domain="docs.myapp.com"
  navigate={navigate}
  style={{
    '--pill-width': '240px',   // collapsed width (default: 300px)
    '--pill-expand': '80px',   // extra width added on focus (default: 100px)
  } as React.CSSProperties}
/>
```

The expanded width is always `pill-width + pill-expand`, so expansion stays **relative** regardless of the base size. Both values are clamped to `100vw - 32px` on mobile so the pill never overflows the viewport.

### Inverted theme for visibility

The default pill uses `bg-background`, so it blends into pages with the same background color. To make the pill stand out, use the **opposite theme** of your page:

```tsx
function App() {
  const isDark = useIsDarkMode() // your app's dark mode detection

  return (
    <ChatWidget
      domain="docs.myapp.com"
      theme={isDark ? 'light' : 'dark'}
      navigate={navigate}
    />
  )
}
```

This renders a dark pill on light pages and a light pill on dark pages, making the widget immediately visible.

## Custom trigger

Replace the default textarea pill with your own trigger component. The component receives an `onClick` prop to toggle the chat drawer:

```tsx
function MyTrigger({ onClick }: { onClick: () => void }) {
  return (
    <button
      onClick={onClick}
      style={{
        position: 'fixed',
        bottom: 24,
        right: 24,
        padding: '12px 20px',
        borderRadius: 12,
        background: '#0a0a0a',
        color: '#fff',
        cursor: 'pointer',
      }}
    >
      💬 Ask AI
    </button>
  )
}

<ChatWidget
  domain="docs.myapp.com"
  trigger={MyTrigger}
  navigate={navigate}
/>
```

Custom triggers render **outside** the `.holocron-chat` container so you can style them with your own page CSS. The default pill is hidden when a custom trigger is provided.

## Programmatic control

The `useChatWidget()` hook gives you full control over the chat from anywhere in your app:

```tsx
import { useChatWidget } from '@holocron.so/vite/chat'

function ChatControls() {
  const { isOpen, isGenerating, messages, open, close, toggle, clear } =
    useChatWidget()

  return (
    <div>
      <p>Chat is {isOpen ? 'open' : 'closed'}</p>
      <p>{messages.length} messages</p>
      <button onClick={toggle}>Toggle</button>
      <button onClick={clear}>New chat</button>
    </div>
  )
}
```

### Hook API

<ResponseField name="isOpen" type="boolean">
  Whether the chat drawer is currently open.
</ResponseField>

<ResponseField name="isGenerating" type="boolean">
  Whether the AI is currently generating a response.
</ResponseField>

<ResponseField name="messages" type="ChatMessage[]">
  Array of all messages in the current conversation.
</ResponseField>

<ResponseField name="open" type="() => void">
  Open the chat drawer.
</ResponseField>

<ResponseField name="close" type="() => void">
  Close the chat drawer.
</ResponseField>

<ResponseField name="toggle" type="() => void">
  Toggle the chat drawer open/closed.
</ResponseField>

<ResponseField name="clear" type="() => void">
  Start a new conversation. The previous conversation stays in the session list and can be reopened from the session select dropdown.
</ResponseField>

## Client-side tools

Tools let the AI **execute actions in the browser**. Define tools with a Zod input schema and a `run` function:

```tsx
import { defineTool } from '@holocron.so/vite/chat'
import { z } from 'zod'

const timeTool = defineTool({
  name: 'get_time',
  description: 'Get the current date and time.',
  input: z.object({}),
  async run() {
    return { time: new Date().toISOString() }
  },
})
```

Pass tools to the widget via the `tools` prop:

```tsx
<ChatWidget
  domain="docs.myapp.com"
  tools={[timeTool]}
  navigate={navigate}
/>
```

<Aside>
  <Info>
    Tools are automatically registered on `document.modelContext` (the [WebMCP](https://purl.org/nickreserved/modelcontext) standard) so browser AI agents can discover them. Set `exposeToModelContext: false` to keep a tool internal to the holocron widget.
  </Info>
</Aside>

### Tool naming

Tool names must be **1-64 characters**, alphanumeric, underscore, or hyphen. The name `bash` is reserved for the server-side docs search tool.

### Human-readable labels

A `description` string property is automatically added to every tool's input schema. The model fills it with a short summary like "Get the current time", which is shown as the tool call label in the chat UI. Your `run` function receives it inside `input` but can ignore it.

### Browser automation tools

`pageTools()` generates a set of browser automation tools from page declarations. These tools let the AI **navigate pages, fill inputs, and highlight elements** with an onboarding-style spotlight:

```tsx
import { pageTools } from '@holocron.so/vite/chat'

const browserTools = pageTools([
  {
    path: '/settings',
    description: 'User settings page with email, name, and notification preferences.',
    actions: [
      {
        name: 'update_email',
        description: 'Type into the email input',
        selector: 'input[name=email]',
      },
      {
        name: 'save',
        description: 'Save settings',
        selector: 'button[data-action=save]',
      },
    ],
  },
  {
    path: '/billing',
    description: 'Billing page with subscription management.',
  },
])

<ChatWidget
  domain="docs.myapp.com"
  tools={browserTools}
  navigate={navigate}
/>
```

This generates five tools:

| Tool                | Description                                                               |
| ------------------- | ------------------------------------------------------------------------- |
| `browser_navigate`  | Navigate to a declared page path                                          |
| `browser_type`      | Type text into an input or textarea                                       |
| `browser_select`    | Select a value in a dropdown                                              |
| `browser_highlight` | Show a persistent spotlight overlay on an element with a description card |
| `browser_read_page` | List all interactive elements on the current page                         |

The AI uses `browser_highlight` to point users at elements instead of clicking them directly. This keeps the user in control. For example, if a user asks "change my email", the AI types the new value with `browser_type`, then highlights the Save button with a message like "Click here to save".

## Tool approvals

Protect sensitive actions by requiring user approval before a tool executes. The widget shows an **Approve/Deny prompt** with a description of the action.

### Per-tool approval

Set `needsApproval` on the tool definition:

```tsx
const deleteTool = defineTool({
  name: 'delete_account',
  description: 'Delete the user account.',
  input: z.object({ confirm: z.boolean() }),
  needsApproval: true, // always ask
  async run({ input }) {
    // only runs after user approves
    await deleteAccount()
    return { deleted: true }
  },
})
```

`needsApproval` can also be a function that returns `true`, `false`, or `{ message: string }` with a custom confirmation message:

```tsx
needsApproval: ({ input }) => {
  if (input.force) return { message: 'This will permanently delete all data.' }
  return false
}
```

### DOM-based approval

For browser automation tools (`browser_type`, `browser_select`), wrap sensitive elements with `data-holocron-requires-approval`:

```html
<div data-holocron-requires-approval="This will delete your account">
  <button data-action="delete-account">Delete account</button>
</div>
```

When the AI tries to interact with an element inside this container, the widget shows the approval prompt with the attribute's value as the confirmation message.

## Context injection

Pass contextual information to the AI via the `context` prop. The object is serialized as XML in the system prompt:

```tsx
<ChatWidget
  domain="docs.myapp.com"
  context={{
    userId: 'u_123',
    email: 'user@example.com',
    plan: 'pro',
    currentProject: { name: 'My App', id: 'prj_abc' },
  }}
  navigate={navigate}
/>
```

The AI can reference this context when answering questions. For example, it knows the user's plan and can tailor responses about feature availability.

## Session persistence

Chat conversations **survive page refreshes**. The widget automatically persists and restores sessions:

* **Same-origin** (widget on the same domain as the docs site): sessions are stored via a first-party cookie (`holocron_chat`).
* **Cross-origin** (widget on a different domain): sessions are stored in `localStorage` and sent via the `x-holocron-chat-session` header.

The **session select dropdown** in the chat drawer header lets users switch between past conversations. Session metadata (titles, timestamps) is stored client-side in `localStorage`; the server stores only the message history.

### Session titles

On the first message of a new conversation, the gateway generates a short title automatically. Until the title arrives, the session select shows a truncated preview of the first message. Starting a new chat via `clear()` rotates to a fresh session; the previous conversation stays in the list.

## Default pill behavior

When no custom `trigger` is provided, the widget shows a **textarea pill** at the **bottom center** of the page, 300px wide (full width minus padding on small screens).

On focus or when text is entered, the pill **expands** to 400px with a smooth width transition. The drawer stays closed while you type. Pressing Enter or clicking the send button submits the message and morphs the pill into the full chat drawer.

The pill uses a `backdrop-filter: blur(10px)` surface with layered shadows for depth, matching the design language of [fin.ai](https://fin.ai).


---
title: Holocron Deploy
url: "https://holocron.so/docs/deploy/holocron.md"
description: Deploy to a hosted holocron.so subdomain with the built-in CLI.
---

{/* Built-in hosted deployment docs for the Holocron CLI deploy command. */}

# Holocron Deploy

`holocron deploy` builds your docs site and uploads it to Holocron hosting. It is
the fastest way to publish a site without managing your own server or Cloudflare
Worker.

```bash
npx -y "@holocron.so/cli" deploy
```

The command runs your build, uploads only changed files, finalizes the deployment,
and prints the live URL.

## Deployment URLs

The URL format depends on how you deploy.

**From GitHub Actions (OIDC)**: the subdomain is derived from your GitHub **repo
name** and **owner**, verified from the OIDC token. Pushing to the default branch
(usually `main`) deploys to:

```txt
https://<repo>-<owner>-site.holocron.so
```

For example, `remorses/my-docs` on `main` → `https://my-docs-remorses-site.holocron.so`.

Pushes to non-default branches or pull request events produce a unique preview URL
that includes the branch name. The deploy command prints the URL after each deploy,
and sets it as a GitHub Actions step output (`holocron_url`).

**From local or API key**: the subdomain uses the project ID instead of the GitHub
repo name, since there is no verified GitHub context:

```txt
https://<projectId>-site.holocron.so
```

Custom domains are not supported yet. Use [Cloudflare Workers](/docs/deploy/cloudflare)
or [Node.js](/docs/deploy/node) if you need your own domain today.

## Authentication

**Auth priority** (first match wins):

1. **`HOLOCRON_KEY`** env var — API key scoped to a specific project. The server resolves the org and project from the key. No flags needed.
2. **Session token** from `npx -y "@holocron.so/cli" login` — if the account has multiple projects, pass `--project prj_xxx` or the CLI prompts interactively.
3. **GitHub Actions OIDC** — automatic when running in GitHub Actions with `permissions: id-token: write`. No API key or login needed if the repo owner matches the Holocron account.

For local deploys, log in once:

```bash
npx -y "@holocron.so/cli" login
npx -y "@holocron.so/cli" deploy
```

For CI deploys, use a project API key:

```bash
HOLOCRON_KEY=holo_xxx npx -y "@holocron.so/cli" deploy
```

No project ID is saved locally on disk. It is either derived from the API key,
passed via `--project`, or selected interactively at deploy time.

### GitHub Actions OIDC (keyless deploys)

The deploy command does not need an API key when run inside GitHub Actions and
the user signed in to holocron.so with the same GitHub account that owns the
repo. Holocron verifies the GitHub OIDC token to authenticate. The workflow needs:

```yaml
permissions:
  id-token: write
```

**How branch detection works:**

* **Pull requests**: uses the `head_ref` OIDC claim (the PR source branch). Marked as preview automatically.
* **Pushes**: uses the `ref` claim stripped to branch name (`refs/heads/main` → `main`). Compared against the project's default branch to decide production vs preview.
* **Explicit override**: `--branch <name>` CLI flag.

### Organizations and project resolution

Each user gets a **personal org** auto-created on first login. All projects and
API keys are scoped to an org. Run `npx -y "@holocron.so/cli" whoami` to see all orgs
and projects.

When a user belongs to multiple orgs, `projects create` prompts which org to
use. Pass `--org <orgId>` to skip the prompt:

```bash
npx -y "@holocron.so/cli" projects create --name "My Docs" --org <orgId>
```

The deploy command resolves the org from the project itself, so `--org` is not
needed for deploys.

## GitHub Actions outputs

The deploy command sets GitHub Actions step outputs when `GITHUB_OUTPUT` is
available:

```txt
holocron_url=https://...
holocron_deployment_id=dep_...
```

## Build output

`holocron deploy` uses the special deploy build output at `dist/.holocron/`.
The server bundle is uploaded as Worker files and the client bundle is uploaded
as static assets.

See [Build Output](/docs/deploy/build-output) for the directory structure.


---
title: Node.js
url: "https://holocron.so/docs/deploy/node.md"
description: "Run vite build to produce a standalone Node.js server, then deploy to any platform that supports Node like Railway, Fly, or a VPS."
---

# Node.js Deployment

Holocron builds a server-rendered application that runs on Node.js.

## Build

```bash
npx vite build
```

This outputs a production bundle to `dist/`.

## Start the server

```bash
node dist/rsc/index.js
```

The server listens on port 3000 by default.

## Production setup

For a production deployment, you need:

1. **Build** the site (`vite build`)
2. **Start** the Node.js server
3. **Reverse proxy** (nginx, Caddy, etc.) for TLS and domain routing

### Example with Docker

```dockerfile
FROM node:22-slim
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
EXPOSE 3000
CMD ["node", "dist/rsc/index.js"]
```

### Example with systemd

```ini
[Unit]
Description=Docs site
After=network.target

[Service]
Type=simple
WorkingDirectory=/opt/docs
ExecStart=/usr/bin/node dist/rsc/index.js
Restart=on-failure

[Install]
WantedBy=multi-user.target
```

## Environment variables

| Variable | Default | Description        |
| -------- | ------- | ------------------ |
| `PORT`   | 3000    | Server listen port |


---
title: Cloudflare Workers
url: "https://holocron.so/docs/deploy/cloudflare.md"
description: Build your docs site and deploy to Cloudflare Workers with wrangler. Includes wrangler.jsonc setup and compatibility flags.
---

# Cloudflare Workers

Holocron can be deployed to Cloudflare Workers for edge-rendered docs with global distribution.

## Setup

Install wrangler and the Cloudflare Vite plugin:

```bash
pnpm add -D wrangler @cloudflare/vite-plugin
```

Add the Cloudflare plugin after Holocron in `vite.config.ts`:

```ts
import { cloudflare } from '@cloudflare/vite-plugin'
import { holocron } from '@holocron.so/vite'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    holocron(),
    cloudflare({
      viteEnvironment: {
        name: 'rsc',
        childEnvironments: ['ssr'],
      },
    }),
  ],
})
```

Create a `wrangler.json` or `wrangler.jsonc`:

```jsonc
{
  "name": "my-docs",
  "main": "spiceflow/cloudflare-entrypoint",
  "compatibility_date": "2026-04-13",
  "compatibility_flags": ["nodejs_compat"]
}
```

## Build and deploy

```bash
npx vite build
npx wrangler deploy
```

## Serving docs under a subpath

Set **`base`** in `vite.config.ts` to mount the whole site under a path prefix, for example when the docs live at `example.com/docs` behind a route:

```ts
export default defineConfig({
  base: '/docs',
  plugins: [holocron(), cloudflare({ /* ... */ })],
})
```

Holocron nests the client build output under a folder matching the base, so Cloudflare serves `/docs/assets/app.js` from `dist/client/docs/assets/app.js`:

```diagram
  vite.config.ts                build output                        request
  ┌───────────────────┐        ┌───────────────────────────┐       ┌────────────────────────┐
  │ base: '/docs'     │───────>│ dist/client/              │       │ GET /docs/assets/app.js│
  │                   │        │   .assetsignore           │       └───────────┬────────────┘
  │ HTML references   │        │   docs/assets/app.js  <───┼───────────────────┘
  │ /docs/assets/*    │        │   docs/icons/logo.svg     │        served by the CDN,
  └───────────────────┘        └───────────────────────────┘        no Worker invocation
```

<Aside>
  <Note>
    Cloudflare's Asset Worker looks paths up in the uploaded directory tree and never strips Vite's `base`, so this nesting is what makes subpath hosting work. It happens automatically; no `ASSETS` binding or extra config needed.
  </Note>
</Aside>

## Custom entry with Workers

If you use a [custom entry](/docs/custom-entry), your Spiceflow app runs as the Worker entry point. You can add Cloudflare-specific bindings (KV, D1, AI) alongside your docs.

## Example project

See the `example-cloudflare/` directory in the Holocron repo for a minimal Cloudflare Workers deployment.


---
title: Build Output
url: "https://holocron.so/docs/deploy/build-output.md"
description: What vite build produces and how to serve it.
---

# Build Output

Running `npx vite build` produces a `dist/` directory with everything needed to serve your docs site.

## Normal build directory structure

```diagram
dist/
├── rsc/
│   ├── index.js          # server entry
│   └── ...
├── client/               # browser assets, CSS, JS, public files
└── holocron-cache.json   # Navigation cache for fast rebuilds
```

## Cache for fast rebuilds

`dist/holocron-cache.json` contains the enriched navigation tree with git SHA hashes for each page. On subsequent builds, pages with unchanged SHAs are reused without re-parsing. Caching `dist/` between CI runs gives near-instant rebuilds.

## Static assets

Fonts, processed images, and bundled JavaScript are placed in the client output. These are fingerprinted with content hashes for long-term caching.

## Deploy build output

The `holocron deploy` CLI builds to `dist/.holocron/` before upload. It uploads `dist/.holocron/rsc` as Worker files and `dist/.holocron/client` as static assets.

## Self-contained output

The production output is self-contained. Navigation, frontmatter, image metadata, and cache data are prepared at build time. Page requests still parse and render the MDX source at runtime through React Server Components.

## Build errors

Production builds **fail** when content errors are detected. Holocron processes every page first, logs all errors to the terminal, then fails the build with a summary. This ensures you see every issue at once instead of fixing them one at a time.

The build fails on:

* **MDX parse errors** — syntax errors in your MDX files
* **MDX component errors** — unknown component names, invalid props
* **Broken internal links** — links pointing to pages that don't exist (see [Broken Link Detection](/docs/create/broken-links))
* **Broken asset references** — images or media files that can't be found on disk

In **dev mode**, these are shown as warnings without stopping the server, so you can iterate freely.

### Skipping build errors

If you need to deploy despite content errors, set the `HOLOCRON_SKIP_BUILD_ERRORS` environment variable:

```bash
HOLOCRON_SKIP_BUILD_ERRORS=true npx vite build
```

Pages with errors are excluded from the build output. They return a 404 instead of rendering broken content.


---
title: Subpath Hosting
url: "https://holocron.so/docs/deploy/base-path.md"
description: Host your docs at a subpath like /docs on your own domain.
---

# Subpath Hosting

<Note>
  Subpath hosting is a **Pro** feature. [See pricing](/docs/pricing) for details.
</Note>

Host your documentation at a subpath on your existing domain, like `yoursite.com/docs`, instead of a separate subdomain. This keeps your docs and product on the same domain for a seamless user experience and better SEO.

## Why host at a subpath

**SEO authority.** Search engines treat subdomains as separate sites. Hosting docs at `yoursite.com/docs` instead of `docs.yoursite.com` means all search ranking authority stays on your main domain.

**Unified experience.** Users never leave your domain. Navigation between your product and documentation feels like one site.

**Professional branding.** A single domain looks cleaner than a mix of subdomains. Share `yoursite.com/docs/quickstart` instead of `docs.yoursite.com/quickstart`.

## Deploy with a base path

<Aside>
  <Info>
    Subpath hosting requires a **Holocron Pro** subscription. [Subscribe from your dashboard](/dashboard).
  </Info>
</Aside>

Add `--base-path` to your deploy command. The path is the subpath prefix where your docs will live:

```bash
HOLOCRON_KEY=holo_xxx npx -y "@holocron.so/cli" deploy --base-path /docs
```

This builds your site with all routes and assets prefixed under `/docs/` and deploys it to your holocron.so URL. Your deployed site serves pages at `/docs/quickstart`, `/docs/api-reference`, etc.

The deploy prints a URL like:

```txt
https://docs-base-my-docs-remorses-site.holocron.so
```

Base-path deployments get their own subdomain (`{base}-base-{project}-site.holocron.so`) so they coexist with root deployments on the same project. Your docs are accessible at the printed URL under the `/docs/` path.

See [Holocron Deploy](/docs/deploy/holocron) for all authentication options (API key, session login, GitHub Actions OIDC).

## Connect to your domain

After deploying, configure your framework or web server to forward requests under `/docs/*` to the deployed holocron.so URL. The user's browser sees `yoursite.com/docs/quickstart`, but the content is served from holocron.so.

```diagram
  yoursite.com                              holocron.so hosting
  ┌─────────────────────────┐               ┌────────────────────────────────┐
  │  /           > home     │               │  {base}-base-{project}-site    │
  │  /pricing    > page     │               │  .holocron.so                  │
  │  /docs/*     > proxy    │──────────────>│  serves /docs/* routes         │
  └─────────────────────────┘               └────────────────────────────────┘
```

Choose your framework below for the specific rewrite or proxy configuration.

### Next.js

Add a rewrite in `next.config.js` to forward `/docs` requests to your deployed URL:

```js
// next.config.js
const DOCS_URL = 'https://docs-base-my-docs-remorses-site.holocron.so'

module.exports = {
  async rewrites() {
    return [
      {
        source: '/docs',
        destination: `${DOCS_URL}/docs`,
      },
      {
        source: '/docs/:path*',
        destination: `${DOCS_URL}/docs/:path*`,
      },
    ]
  },
}
```

### Vercel

Add rewrites to your `vercel.json`:

```json
{
  "rewrites": [
    {
      "source": "/docs",
      "destination": "https://docs-base-my-docs-remorses-site.holocron.so/docs"
    },
    {
      "source": "/docs/:path*",
      "destination": "https://docs-base-my-docs-remorses-site.holocron.so/docs/:path*"
    }
  ]
}
```

This works for any Vercel-hosted frontend, regardless of framework.

### React Router

Create a splat resource route at `app/routes/docs.$.ts`. A resource route has no default export, so React Router treats it as a pure server endpoint. The `loader` handles GET requests and `action` handles everything else:

```ts
// app/routes/docs.$.ts
const DOCS_URL = 'https://docs-base-my-docs-remorses-site.holocron.so'

export async function loader({ request }: { request: Request }) {
  const url = new URL(request.url)
  const target = new URL(url.pathname + url.search, DOCS_URL)
  return fetch(target, {
    headers: {
      'X-Forwarded-Host': url.hostname,
    },
  })
}

export async function action({ request }: { request: Request }) {
  const url = new URL(request.url)
  const target = new URL(url.pathname + url.search, DOCS_URL)
  return fetch(target, {
    method: request.method,
    headers: request.headers,
    body: request.body,
  })
}
```

Also add `app/routes/docs._index.ts` so bare `/docs` is handled:

```ts
// app/routes/docs._index.ts
export { loader, action } from './docs.$.ts'
```

### TanStack Start

Create a server route at `app/routes/docs/$.ts`. TanStack Start uses `createAPIFileRoute` from `@tanstack/react-start/api` for server-only routes that return raw `Response` objects. The file path `docs/$.ts` maps to the `/docs/$` catch-all:

```ts
// app/routes/docs/$.ts
import { createAPIFileRoute } from '@tanstack/react-start/api'

const DOCS_URL = 'https://docs-base-my-docs-remorses-site.holocron.so'

function proxy(request: Request) {
  const url = new URL(request.url)
  const target = new URL(url.pathname + url.search, DOCS_URL)
  return fetch(target, {
    method: request.method,
    headers: { 'X-Forwarded-Host': url.hostname },
    body: request.method !== 'GET' && request.method !== 'HEAD'
      ? request.body
      : undefined,
  })
}

export const APIRoute = createAPIFileRoute('/docs/$')({
  GET: ({ request }) => proxy(request),
  POST: ({ request }) => proxy(request),
})
```

The `_splat` param captures everything after `/docs/`, but since the proxy forwards `url.pathname` directly, it works without parsing it.

### Cloudflare Workers

Create a Cloudflare Worker that proxies `/docs` requests:

```js
export default {
  async fetch(request) {
    const url = new URL(request.url)

    if (url.pathname === '/docs' || url.pathname.startsWith('/docs/')) {
      const DOCS_URL = 'https://docs-base-my-docs-remorses-site.holocron.so'
      const proxyUrl = new URL(url.pathname + url.search, DOCS_URL)
      const proxyReq = new Request(proxyUrl, request)
      proxyReq.headers.set('X-Forwarded-Host', url.hostname)
      return fetch(proxyReq)
    }

    // Pass through to your origin for everything else
    return fetch(request)
  },
}
```

### Nginx

Add a `location` block to your Nginx config:

```nginx
location /docs/ {
    proxy_pass https://docs-base-my-docs-remorses-site.holocron.so/docs/;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Real-IP $remote_addr;
}

location = /docs {
    return 301 /docs/;
}
```

### Express

Use `http-proxy-middleware` in your Express app:

```js
const { createProxyMiddleware } = require('http-proxy-middleware')

app.use(
  '/docs',
  createProxyMiddleware({
    target: 'https://docs-base-my-docs-remorses-site.holocron.so',
    changeOrigin: true,
  }),
)
```

## GitHub Actions

Use `--base-path` in your CI workflow the same way:

```yaml
name: Deploy docs
on:
  push:
    branches: [main]

permissions:
  id-token: write

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm install
      - run: npx -y "@holocron.so/cli" deploy --base-path /docs
```

## Self-hosted base path

If you self-host your docs (Node.js, Cloudflare Workers, Docker), configure Vite's `base` option directly instead of using `--base-path`:

```ts
// vite.config.ts
import { defineConfig } from 'vite'
import { holocron } from '@holocron.so/vite'

export default defineConfig({
  base: '/docs/',
  plugins: [holocron()],
})
```

Then set up a reverse proxy in front of your self-hosted server pointing `/docs/*` to the docs server.


---
title: Multi-Tenant Deployment
url: "https://holocron.so/docs/deploy/multi-tenant.md"
description: "Build once, deploy many tenants instantly by swapping data chunks."
---

Holocron's build output splits into three layers: **framework code** (shared, stable), **site data** (config, navigation, MDX loaders), and **page content** (one file per page). For multi-tenant platforms, you build once and swap only the data and page layers per tenant.

This means deployments go from minutes to milliseconds. The framework chunk is uploaded once and reused across all tenants via content-addressable storage.

## Build output anatomy

```diagram
dist/rsc/assets/
├── holocron-stable-{hash}.js           # framework + components (shared)
├── holocron-data.js                    # config, navigation, MDX loaders (per tenant)
├── holocron-page-index-{hash}.js       # page content (per tenant)
├── holocron-page-getting-started-{hash}.js
└── ...fonts, icons, runtime
```

| Layer                            | Changes per tenant? | Naming                 | Size           |
| -------------------------------- | ------------------- | ---------------------- | -------------- |
| `holocron-stable-{hash}.js`      | No                  | Content-hashed         | \~2-6 MB       |
| `holocron-data.js`               | Yes                 | Deterministic, no hash | \~5-50 KB      |
| `holocron-page-{slug}-{hash}.js` | Yes                 | Deterministic per slug | \~1-10 KB each |
| Client JS/CSS                    | No                  | Content-hashed         | \~3-10 MB      |

The **stable chunk** contains all React, spiceflow, MDX components (accordion, tabs, code blocks, OpenAPI renderer, etc.). Tree shaking does not remove unused components because the MDX component registry imports everything unconditionally; the bundler can't know which component names appear in MDX content.

## Pipeline overview

```diagram
┌─────────────────────────────────────────────────────────────────────────┐
│  1. Build once                                                          │
│     npx vite build                                                      │
│     > produces dist/ with stable chunks + template data                 │
└────────────────────────────────────┬────────────────────────────────────┘
                                     │
         ┌───────────────────────────┼───────────────────────────┐
         v                           v                           v
┌─────────────────┐     ┌─────────────────┐         ┌─────────────────┐
│  Tenant A       │     │  Tenant B       │         │  Tenant C       │
│                 │     │                 │         │                 │
│  generateData() │     │  generateData() │         │  generateData() │
│  > data.js      │     │  > data.js      │         │  > data.js      │
│  > page chunks  │     │  > page chunks  │         │  > page chunks  │
└────────┬────────┘     └────────┬────────┘         └────────┬────────┘
         │                       │                           │
         v                       v                           v
┌─────────────────────────────────────────────────────────────────────────┐
│  Deploy (content-addressable)                                           │
│  Shared stable chunk uploaded once. Only data.js + pages per tenant.    │
└─────────────────────────────────────────────────────────────────────────┘
```

## Generating tenant data

Use `generateHolocronData` to produce the data chunk and page chunks for a tenant without running a full Vite build.

```ts
import {
  normalizeConfig,
  generateHolocronData,
} from '@holocron.so/vite'
import fs from 'node:fs'
import path from 'node:path'

// 1. Load and normalize the tenant's docs.json
const raw = JSON.parse(fs.readFileSync('./tenant-a/docs.json', 'utf-8'))
const config = normalizeConfig(raw)

// 2. Collect all page slugs from the config navigation
const slugs = collectSlugsFromConfig(config)

// 3. Generate data chunk + page chunks
const result = await generateHolocronData({
  config,
  getMdxSource: async (slug) => {
    return fs.readFileSync(`./tenant-a/pages/${slug}.mdx`, 'utf-8')
  },
  slugs,
  base: '/',
})

// 4. Write to the deploy directory (copy dist/ first, then overwrite)
const assetsDir = './deploy/tenant-a/rsc/assets'
fs.writeFileSync(path.join(assetsDir, 'holocron-data.js'), result.dataChunkSource)

for (const [slug, chunk] of result.pageChunks) {
  fs.writeFileSync(path.join(assetsDir, chunk.filename), chunk.source)
}
```

<Aside>
  <Note>
    `generateHolocronData` is pure computation. It doesn't touch the filesystem, Vite, or git. You provide the config object and an async MDX loader; it returns JS source strings.
  </Note>
</Aside>

### Loading MDX from a database or API

The `getMdxSource` callback can load content from anywhere. For a CMS-backed platform:

```ts
const result = await generateHolocronData({
  config,
  getMdxSource: async (slug) => {
    const row = await db.query('SELECT content FROM pages WHERE slug = ?', [slug])
    return row.content
  },
  slugs,
})
```

### Collecting slugs from config

The config navigation tree contains all page slugs. Walk it to collect them:

```ts
function collectSlugsFromConfig(config) {
  const slugs = []
  for (const tab of config.navigation.tabs) {
    for (const group of tab.groups) {
      walkGroup(group, slugs)
    }
  }
  return slugs
}

function walkGroup(group, slugs) {
  for (const entry of group.pages) {
    if (typeof entry === 'string') {
      slugs.push(entry)
    } else if ('pages' in entry) {
      walkGroup(entry, slugs)
    }
  }
}
```

## Deploying with content-addressable uploads

Holocron's deploy API uses **content-addressable storage**: each file is SHA-256 hashed, and only new hashes are uploaded. This is what makes multi-tenant deployments fast.

### First tenant deploy

All files are new. The stable chunk (\~5 MB), client assets (\~5 MB), data chunk, and page chunks are all uploaded.

### Subsequent tenant deploys

The stable chunk and client assets already exist in storage (same hashes). Only the tenant-specific `holocron-data.js` and `holocron-page-*.js` files are new. A typical tenant deploy uploads **50-100 KB** instead of 10+ MB.

```ts
import { createHash } from 'node:crypto'

// Collect all files from the deploy directory
const files = collectFiles('./deploy/tenant-a')

// Hash each file
const manifest = files.map(f => ({
  path: f.relativePath,
  hash: createHash('sha256').update(f.content).digest('hex'),
}))

// POST to create deployment — server returns which hashes already exist
const { deploymentId, existingHashes } = await api.createDeployment({ files: manifest })

// Upload only new files
const newFiles = files.filter(f => !existingHashes.includes(f.hash))
await api.uploadFiles(deploymentId, newFiles)

// Finalize — site goes live instantly
await api.finalizeDeployment(deploymentId)
```

## Per-tenant deploy directory

The simplest approach: copy the full `dist/` from the template build, then overwrite just the data files.

```ts
import { cpSync, writeFileSync } from 'node:fs'

// Copy the template build
cpSync('./dist', `./deploys/${tenantId}`, { recursive: true })

// Overwrite data layer with tenant-specific content
const assetsDir = `./deploys/${tenantId}/rsc/assets`

// Remove the template data files
for (const f of readdirSync(assetsDir)) {
  if (f === 'holocron-data.js' || f.startsWith('holocron-page-')) {
    unlinkSync(path.join(assetsDir, f))
  }
}

// Write tenant data files
writeFileSync(path.join(assetsDir, 'holocron-data.js'), result.dataChunkSource)
for (const [, chunk] of result.pageChunks) {
  writeFileSync(path.join(assetsDir, chunk.filename), chunk.source)
}
```

## OpenAPI pages

Multi-tenant sites can generate OpenAPI endpoint pages without a Vite build. The `generateOpenAPIPages` function takes a raw spec (YAML, JSON, or parsed object) and produces MDX pages with the `<OpenAPIEndpoint>` component, which is already bundled in the stable shell chunk.

```ts
import {
  normalizeConfig,
  generateHolocronData,
  generateOpenAPIPages,
} from '@holocron.so/vite'

// 1. Generate OpenAPI pages from a spec
const { pages: apiPages, navigation: apiNavGroups } = await generateOpenAPIPages({
  spec: openApiYamlString, // YAML, JSON string, or parsed object
  slugPrefix: 'api',       // pages land at api/get-users, api/post-users, etc.
})

// 2. Merge OpenAPI pages into the tenant's page map
const allPages = { ...tenantPages, ...apiPages }
const allSlugs = Object.keys(allPages)

// 3. Merge OpenAPI navigation into the config
const config = normalizeConfig({
  ...tenantDocsJson,
  navigation: {
    ...tenantDocsJson.navigation,
    tabs: [
      ...tenantDocsJson.navigation.tabs,
      { tab: 'API Reference', groups: apiNavGroups },
    ],
  },
})

// 4. Generate data as usual
const result = await generateHolocronData({
  config,
  getMdxSource: async (slug) => allPages[slug] ?? '',
  slugs: allSlugs,
})
```

`generateOpenAPIPages` processes the spec through the same pipeline as the Vite plugin: `@scalar` bundling, 3.1 upgrade, full dereferencing. Each operation becomes one MDX page with curl examples, request/response examples, and the interactive `<OpenAPIEndpoint>` component.

The `navigation` output groups endpoints by their first OpenAPI tag, ready to drop into a tab's `groups` array.

## Custom CSS

Set `customCss` in the tenant's docs.json to inject arbitrary CSS at runtime. This is useful for multi-tenant sites where users cannot provide CSS files at build time.

```json
{
  "name": "My Docs",
  "customCss": ".editorial-heading { font-weight: 800; } .slot-navbar { border-bottom: 2px solid var(--primary); }"
}
```

The CSS string is injected as a `<style>` tag alongside the theme's color and font styles.

## Custom fonts

Set `fonts` in docs.json to use Google Fonts or custom web fonts. The shell loads them at runtime via Google Fonts API.

```json
{
  "fonts": {
    "heading": {
      "family": "Space Grotesk",
      "weight": 700
    },
    "body": {
      "family": "Inter"
    }
  }
}
```

No build step needed. The font family is applied via CSS custom properties (`--font-sans`, `--font-heading`).

## Search

Sidebar search works automatically in multi-tenant mode. It uses [Orama](https://orama.com) to build a full-text index **client-side** from the navigation tree. Page titles, descriptions, and headings are all indexed.

No build-time search index generation is needed. The navigation data in `holocron-data.js` contains all the metadata Orama needs.

## Feature support

| Feature                           | Multi-tenant support             |
| --------------------------------- | -------------------------------- |
| MDX pages (all content)           | Works                            |
| Navigation (tabs, groups, nested) | Works                            |
| Code blocks (Shiki highlighting)  | Works (runtime)                  |
| Mermaid diagrams                  | Works (client-side render)       |
| Math / KaTeX                      | Works (client-side render)       |
| Colors / theming                  | Works via `colors` config        |
| Custom fonts                      | Works via `fonts` config         |
| Custom CSS                        | Works via `customCss` config     |
| Icons (lucide, emoji, URL)        | Works                            |
| Redirects                         | Works                            |
| Sidebar search                    | Works (Orama, client-side)       |
| AI chat widget                    | Works (built into shell)         |
| Anchors                           | Works                            |
| OpenAPI tabs                      | Works via `generateOpenAPIPages` |
| Custom MDX components             | Not supported                    |
| Changelog tabs                    | Not yet supported                |

## What each export does

| Export                          | Purpose                                                                        |
| ------------------------------- | ------------------------------------------------------------------------------ |
| `normalizeConfig(raw)`          | Parse a raw docs.json object into a `HolocronConfig`                           |
| `parseConfigSource(jsonString)` | Parse a JSON/JSONC string into a `HolocronConfig`                              |
| `generateHolocronData(opts)`    | Produce `holocron-data.js` + page chunk sources                                |
| `generateOpenAPIPages(opts)`    | Generate MDX pages + navigation from an OpenAPI spec                           |
| `buildNavigationData(opts)`     | Build the enriched navigation tree (used internally by `generateHolocronData`) |
| `processMdx(content, library)`  | Process a single MDX string into normalized content + metadata                 |
| `collectIconRefs(opts)`         | Collect all icon refs needed for the icon atlas                                |

All functions are importable from `@holocron.so/vite`.


---
title: Components
url: "https://holocron.so/docs/components.md"
description: "Holocron's Mintlify-style component surface, organized like Mintlify's component docs."
---

# Components

This section follows the same broad structure as Mintlify's component docs, but the examples render through **Holocron's actual MDX component map**.

## Browse by use case

<CardGroup cols={3}>
  <Card title="Structure content" icon="layout-grid" href="/docs/components/columns">
    Columns, cards, steps, tiles, and tree layouts.
  </Card>

  <Card title="Draw attention" icon="badge-alert" href="/docs/components/callouts">
    Callouts, badges, banner config, tooltips, and updates.
  </Card>

  <Card title="Show hidden content" icon="chevrons-up-down" href="/docs/components/accordions">
    Accordions, expandables, tabs, and views.
  </Card>

  <Card title="Document APIs" icon="braces" href="/docs/components/fields">
    Fields, responses, request examples, response examples, and panel layouts.
  </Card>

  <Card title="Right sidebar" icon="panel-right" href="/docs/components/aside">
    Place notes, API examples, or a wider rail with Aside.
  </Card>

  <Card title="Visual helpers" icon="palette" href="/docs/components/color">
    Icons, color tokens, frames, mermaid diagrams, and previews.
  </Card>

  <Card title="AI-oriented UI" icon="message-square" href="/docs/components/prompt">
    Prompt cards and examples useful for agent-facing docs.
  </Card>
</CardGroup>

## Importing components in `.tsx` files

All MDX components are available from `@holocron.so/vite/mdx`. This is useful when you build custom components that compose Holocron primitives:

```tsx
import { Card, CardGroup, Callout, Steps, Step } from '@holocron.so/vite/mdx'

export function FeatureGrid({ features }) {
  return (
    <CardGroup cols={3}>
      {features.map((f) => (
        <Card key={f.title} title={f.title} icon={f.icon}>
          {f.description}
        </Card>
      ))}
    </CardGroup>
  )
}
```

You only need this import when writing `.tsx` component files. In MDX pages, all components are available globally without imports.

## What to expect on each page

* A short explanation of the Mintlify component concept
* Holocron examples that actually render in this repo
* A **Holocron differences** section when parity is incomplete or behavior differs

<Info>
  Some Mintlify docs describe behaviors that are broader than Holocron's current implementation. Those differences are called out explicitly instead of being hidden.
</Info>


---
title: Accordions
url: "https://holocron.so/docs/components/accordions.md"
description: Use accordions to collapse and reveal content blocks.
---

# Accordions

Holocron supports Mintlify-style `Accordion` and `AccordionGroup` blocks for toggling sections of content.

<AccordionGroup>
  <Accordion title="Single accordion" description="A standalone disclosure block" defaultOpen icon="circle-help">
    Accordions can contain arbitrary MDX.

    * Lists
    * Callouts

    <Note>Nested callouts render correctly inside the accordion body.</Note>

    ```ts
    console.log('accordion content')
    ```
  </Accordion>
</AccordionGroup>

<AccordionGroup>
  <Accordion title="Grouped accordion A" icon="rocket" defaultOpen>
    First grouped item.
  </Accordion>

  <Accordion title="Grouped accordion B" icon="github">
    Second grouped item.
  </Accordion>
</AccordionGroup>

## Holocron differences

* Holocron currently supports `title`, `description`, `defaultOpen`, `icon`, and `iconType`.
* Mintlify documents hash-linking and explicit accordion ids. Holocron does **not** currently expose that behavior.
* A lone `Accordion` is normalized into an `AccordionGroup` internally, so standalone authored accordions still render.


---
title: Place supporting content in the right sidebar
url: "https://holocron.so/docs/components/aside.md"
description: Place supporting content in the right sidebar. Set width to match API reference pages.
---

# Aside

`Aside` is a **positioning marker**. On desktop its children render in the right sidebar. On mobile they stack after the section. The marker itself has **no visual frame**, so wrap visible content in `Note`, `Tip`, `Panel`, or another framed component.

```mdx
<Aside>
<Note>
This note appears in the right sidebar on desktop.
</Note>
</Aside>
```

<Aside>
  <Note>
    This note appears in the right sidebar on desktop.
  </Note>
</Aside>

## Full height

Use **`full`** so the aside spans every heading after it, until the next `<Aside full>` or the end of the page. Sticky content like a table of contents or API examples should use this.

```mdx
<Aside full>
  <TableOfContentsPanel />
</Aside>
```

## API reference layout

API pages use a **fixed 460px** right rail. Holocron does that automatically when the aside contains **`RequestExample`** or **`ResponseExample`**. Put them in **`<Aside full>`** so the examples stay sticky while you scroll:

````mdx
<Aside full>
<Panel>
  <RequestExample>

```bash
curl https://api.example.com/v1/orders \
  -H "authorization: Bearer $TOKEN"
```

  </RequestExample>

  <ResponseExample>

```json
{ "ok": true }
```

  </ResponseExample>
</Panel>
</Aside>
````

<Aside full>
  <Panel>
    <Aside>
      <RequestExample>
        ```bash
        curl https://api.example.com/v1/orders \
          -H "authorization: Bearer $TOKEN"
        ```
      </RequestExample>
    </Aside>

    <Aside>
      <ResponseExample>
        ```json
        { "ok": true }
        ```
      </ResponseExample>
    </Aside>
  </Panel>
</Aside>

You do **not** need `width` for that. `RequestExample` and `ResponseExample` already bump `--grid-sidebar-width` to **460px**. The page cap stays **1200px**. Extra viewport width becomes **gap**, not a growing rail.

## Change the right sidebar width

Use **`width`** for an explicit pixel size. The scan takes the **max** across all asides on the page, so one `width={480}` sets the whole right rail:

```mdx
<Aside width={480}>
<Note>
This rail is 480px wide.
</Note>
</Aside>
```

To make an API-style page **wider than 460px**, set `width` on the same aside:

````mdx
<Aside full width={560}>
<Panel>
  <RequestExample>

```bash
curl https://api.example.com/v1/orders
```

  </RequestExample>
</Panel>
</Aside>
````

`width="480px"` also works. Percent values like `width="50%"` are ignored.

## Props

<ResponseField name="full" type="boolean">
  Span every heading after this aside until the next `<Aside full>` or the end of the page.
</ResponseField>

<ResponseField name="width" type="number">
  Sidebar width in pixels. The page uses the largest value from `width` and from known components like `RequestExample` (460px).
</ResponseField>

## Holocron differences

* Mintlify's request and response examples use a fixed sidebar you cannot configure.
* Holocron lets you set a **fixed** rail with `<Aside width={N}>`. `RequestExample` and `ResponseExample` already use **460px**.


---
title: Badge
url: "https://holocron.so/docs/components/badge.md"
description: "Use badges for status labels, emphasis, and small metadata tags."
---

# Badge

Use `Badge` to label content with compact, colored status chips.

<Columns cols={2}>
  <Column>
    <Badge color="blue">Beta</Badge>

    <Badge color="green">Stable</Badge>

    <Badge color="orange">Experimental</Badge>

    <Badge color="red">Deprecated</Badge>
  </Column>

  <Column>
    <Badge color="purple" icon="sparkles">AI</Badge>

    <Badge color="gray" size="lg">Large</Badge>

    <Badge color="white" shape="pill">Pill</Badge>

    <Badge color="blue" stroke>Stroke</Badge>
  </Column>
</Columns>

Inline badges also work inside prose: Holocron is currently <Badge color="green">shipping</Badge> a practical subset of Mintlify's component surface.

## Holocron differences

* Holocron supports color variants, icons, `size`, `shape`, `stroke`, and `disabled`.
* This page focuses on render coverage rather than matching Mintlify's exact spacing or palette values.


---
title: Banner
url: "https://holocron.so/docs/components/banner.md"
description: Configure a site-wide banner from docs.json.
---

# Banner

Unlike most component pages, `Banner` is **config-driven** rather than an MDX tag. Add it to your `docs.json` to show a site-wide banner.

```json
{
  "banner": {
    "content": "Holocron is under active development. Browse the [component examples](/docs/components).",
    "dismissible": true
  }
}
```

## Supported behavior

* Site-wide content string
* Basic markdown links in the content
* Optional `dismissible: true`

<Note>
  Holocron dismisses the banner until the content string changes, which matches the intended UX Mintlify documents.
</Note>

## Holocron differences

* Mintlify documents language-specific banner overrides. Holocron does not currently expose a language-aware banner system.
* Content is intentionally simple: short text plus inline links is the safe target.


---
title: Callouts
url: "https://holocron.so/docs/components/callouts.md"
description: "Use semantic callouts to highlight notes, warnings, tips, and important information."
---

# Callouts

Holocron supports the same common callout vocabulary most Mintlify authors expect.

<Note>
  Use `Note` for neutral supporting information.
</Note>

<Warning>
  Use `Warning` when the user should slow down or verify something.
</Warning>

<Info>
  Use `Info` for factual guidance that should stand out from the prose.
</Info>

<Tip>
  Use `Tip` for shortcuts and authoring advice.
</Tip>

<Check>
  Use `Check` when documenting a successful or recommended path.
</Check>

<Danger>
  Use `Danger` for destructive or high-risk actions.
</Danger>

<Callout icon="sparkles" color="#7c3aed">
  Generic `Callout` gives you a custom icon and color when the preset semantic variants are not enough.
</Callout>

## Callouts inside Aside

When placing a callout inside an `<Aside>`, keep it **short** (1-2 sentences). A non-full Aside shares its vertical space with the section it belongs to. If the aside callout is taller than the section text, extra whitespace appears below the main content to fill the gap. Only add aside callouts to sections with enough body text to match the aside height.

## Holocron differences

* The semantic callouts are the easiest way to stay within Holocron's well-tested surface.
* Generic `Callout` is supported, but this site intentionally prefers the semantic variants whenever possible.


---
title: Cards
url: "https://holocron.so/docs/components/cards.md"
description: "Use cards for linked navigation, grouped highlights, and compact summaries."
---

# Cards

`Card` works well for docs homepages, section hubs, and grouped calls to action.

<Columns cols={2}>
  <Card title="Basic linked card" icon="arrow-right" href="/docs/components/code-groups">
    Use a card as a navigational block inside a docs page.
  </Card>

  <Card title="Horizontal card" icon="panel-left" horizontal href="/docs/components/columns" cta="Open page">
    Horizontal layout works for denser landing pages.
  </Card>
</Columns>

<Card title="Image card" icon="image" href="/docs/components/cards" img="https://placehold.co/1200x600/101828/ffffff?text=Holocron+Card+Preview" arrow>
  Cards support an optional `img` prop for a header image. The `arrow` prop adds a directional indicator.
</Card>

## Holocron differences

* Holocron supports `title`, `icon`, `iconType`, icon `color`, `href`, `horizontal`, `img`, `cta`, and `arrow`.
* The layout is intentionally simple and does not try to replicate Mintlify pixel-for-pixel.


---
title: Code Groups
url: "https://holocron.so/docs/components/code-groups.md"
description: Group multiple code blocks into a single tabbed code surface.
---

# Code Groups

Use `CodeGroup` when a single example needs multiple language or package-manager variants. Holocron rewrites it to `Tabs` at parse time, so each titled code fence becomes a tab.

<Tabs items={["app.ts", "docs.json"]}>
  <Tab title="app.ts">
    ```ts
    import { defineConfig } from 'vite'
    import { holocron } from '@holocron.so/vite'

    export default defineConfig({
      plugins: [holocron()],
    })
    ```
  </Tab>

  <Tab title="docs.json">
    ```json
    {
      "name": "Holocron Docs",
      "navigation": [{ "group": "Guides", "pages": ["index"] }]
    }
    ```
  </Tab>
</Tabs>

<Tabs items={["pnpm", "npm"]}>
  <Tab title="pnpm">
    ```bash
    pnpm add @holocron.so/vite react react-dom vite
    ```
  </Tab>

  <Tab title="npm">
    ```bash
    npm install @holocron.so/vite react react-dom vite
    ```
  </Tab>
</Tabs>

## Holocron differences

* Holocron supports the core `CodeGroup` authoring flow: multiple titled fences become tabs.
* Mintlify documents synchronized groups and dropdown presentation. Holocron currently focuses on the tabbed rendering path.


---
title: Color
url: "https://holocron.so/docs/components/color.md"
description: "Show tokens, palettes, and theme-aware color pairs in docs."
---

# Color

Use `Color`, `Color.Row`, and `Color.Item` to document design tokens and palette decisions.

<Color>
  <Color.Row title="Brand palette">
    <Color.Item name="Primary" value="#5b7cff" />

    <Color.Item name="Accent" value="#7c3aed" />

    <Color.Item name="Success" value="#16a34a" />
  </Color.Row>

  <Color.Row title="Theme-aware tokens">
    <Color.Item name="Background" value={{ light: '#ffffff', dark: '#111827' }} />

    <Color.Item name="Foreground" value={{ light: '#111827', dark: '#f9fafb' }} />
  </Color.Row>
</Color>

## Holocron differences

* Holocron's current implementation renders compact token cards instead of Mintlify's fuller palette/table variants.
* Theme-aware values are still useful here because Holocron can display both light and dark token values in one swatch.


---
title: Columns
url: "https://holocron.so/docs/components/columns.md"
description: "Split content into columns for cards, prose blocks, and mixed layouts."
---

# Columns

`Columns` is the layout primitive for multi-column content, while `Column` is helpful when the child is not already a card-like block.

<Columns cols={2}>
  <Card title="Card in a column" icon="blocks">
    Cards can be direct children of `Columns`.
  </Card>

  <Column>
    **Column wrappers** are useful when the content is mixed prose, lists, or nested components.

    * Markdown lists render correctly
    * Nested components can sit below prose

    <Tip>Use `Column` when the child is not already a layout component.</Tip>
  </Column>
</Columns>

<Columns cols={3}>
  <Card title="One" icon="box" />

  <Card title="Two" icon="layers" />

  <Card title="Three" icon="grid-3x3" />
</Columns>

## Holocron differences

* Holocron supports the common `cols={1-4}` usage pattern.
* The responsive details are simpler than Mintlify's marketing docs, but the authoring model is the same.


---
title: Examples
url: "https://holocron.so/docs/components/examples.md"
description: Use request and response examples as a pinned supporting surface.
---

# Examples

Mintlify uses `RequestExample` and `ResponseExample` to create a right-hand code surface. In Holocron, the clearest way to demonstrate that layout is to place them inside an `Aside full` block.

<Aside full>
  <Panel>
    <Aside>
      <RequestExample>
        ```bash
        curl -X POST https://example.com/docs \
          -H "content-type: application/json" \
          -d '{"slug":"components/examples"}'
        ```
      </RequestExample>
    </Aside>

    <Aside>
      <ResponseExample>
        ```json
        {
          "ok": true,
          "page": "components/examples"
        }
        ```
      </ResponseExample>
    </Aside>
  </Panel>
</Aside>

The main page content stays readable while the examples remain visually grouped in the side rail on larger screens.

## Holocron differences

* Mintlify documents additional dropdown-oriented example flows. Holocron currently focuses on the core request/response pair.
* Sidebar placement in Holocron is tied to its editorial layout primitives, so `Aside` placement matters more than in Mintlify's hosted renderer.
* Use [`<Aside width={N}>`](/docs/components/aside) when examples need a wider fixed rail than the default 230px. `RequestExample` already uses 460px.


---
title: Expandables
url: "https://holocron.so/docs/components/expandables.md"
description: Hide nested details behind a disclosure block.
---

# Expandables

Use `Expandable` when a page needs optional detail without creating a full accordion list.

<Expandable title="Show advanced options" defaultOpen>
  Expandables work well for secondary details, object internals, or long examples that should not dominate the page.

  * Nested markdown is supported
  * Code blocks are supported

  ```json
  { "advanced": true, "source": "holocron" }
  ```
</Expandable>

## Holocron differences

* Holocron currently supports `title`, `description`, `icon`, `iconType`, `iconLibrary`, `defaultOpen`, and `className`.
* This is a simpler disclosure primitive than Mintlify's richer schema-documentation examples, but it covers the common authoring pattern.


---
title: Fields
url: "https://holocron.so/docs/components/fields.md"
description: Document request and response fields for APIs and structured content.
---

# Fields

Use `ParamField` and `ResponseField` to document structured interfaces inside prose-heavy docs.

<ParamField path="projectId" type="string" required>
  The project identifier in the route path.
</ParamField>

<ParamField query="draft" type="boolean" default="false">
  When true, the preview draft is returned instead of published content.
</ParamField>

<ParamField header="authorization" type="string" required>
  Standard bearer token header.
</ParamField>

<ResponseField name="slug" type="string" required>
  Canonical page slug.
</ResponseField>

<ResponseField name="headings" type="NavHeading[]">
  Flat heading list used by Holocron's table of contents.
</ResponseField>

## Holocron differences

* Holocron supports the common field metadata: location, type, required, and deprecated. `default` is rendered on `ParamField`.
* Mintlify documents a wider prop surface around response formatting. Holocron currently keeps the rendering intentionally small and readable.


---
title: Frames
url: "https://holocron.so/docs/components/frames.md"
description: "Wrap previews, screenshots, and embeds in a framed container."
---

# Frames

Use `Frame` to visually isolate an image, preview, or other embedded content from the surrounding prose.

<Frame caption="Holocron preview frame">
  <div className="rounded-md bg-muted p-6 text-center">Preview content inside a frame</div>
</Frame>

<Frame caption="Frames can contain richer markup">
  <Columns cols={2}>
    <Card title="Inside a frame" icon="monitor">
      Nested content still works.
    </Card>

    <Card title="Useful for comparisons" icon="split-square-vertical">
      Especially when documenting design system pieces.
    </Card>
  </Columns>
</Frame>

## Holocron differences

* Holocron supports framed content plus `description`, `caption`, or `hint` text. If more than one is set, it renders the first available value in that order.
* Mintlify also documents media-specific polish around videos; Holocron's current frame is primarily a visual wrapper.


---
title: Icons
url: "https://holocron.so/docs/components/icons.md"
description: "Render lucide, Font Awesome, emoji, URL, and local SVG icons in MDX."
---

# Icons

Holocron routes icon rendering through a build-time atlas and supports several icon input styles.

<Columns cols={2}>
  <Card title="Default library icon" icon="rocket">
    `rocket`
  </Card>

  <Card title="Emoji icon" icon="✨">
    `✨`
  </Card>

  <Card title="Explicit lucide prefix" icon="lucide:globe">
    `lucide:globe`
  </Card>

  <Card title="Font Awesome brands" icon="fontawesome:brands:github">
    `fontawesome:brands:github`
  </Card>

  <Card title="Local SVG file" icon="/icons/vercel.svg">
    `/icons/vercel.svg`
  </Card>
</Columns>

Inline icons also work in headings and prose: <Icon icon="sparkles" /> Holocron prefers icons that stay readable in both light and dark themes.

## Holocron differences

* Holocron supports emoji, remote URL icons, **local SVG paths** (`/icons/vercel.svg`), default-library names, and explicit prefixed icon references.
* This page is a better compatibility reference than Mintlify's own docs because it renders through Holocron's actual atlas pipeline.

## Local SVG on a page

Put the file in **`public/`** and set `icon` in the page frontmatter:

```mdx
---
$schema: https://holocron.so/frontmatter.json
title: Vercel deployments
sidebarTitle: Vercel
icon: /icons/vercel.svg
---
```

Holocron inlines the SVG so it matches Lucide size and **`currentColor`**. See [Customize icons](/docs/customize/icons) for `docs.json` group icons.


---
title: Marquee
url: "https://holocron.so/docs/components/marquee.md"
description: "Infinite scrolling content with customizable speed, direction, and fade edges."
---

# Marquee

Use `Marquee` to create an infinite scrolling loop of icons, logos, cards, or any content. Works well for partner logos, feature highlights, or testimonial carousels.

## Logo carousel

Scroll partner or integration logos using `Image` with relative paths from your `public/` folder. Set a uniform `height` to keep all logos visually aligned; the width scales proportionally. Add `disableZoom` so clicking a scrolling logo does not open the zoom dialog.

```mdx
<Marquee duration={20} slowOnHover gap={24}>
  <Image src="/logos/vercel.svg" alt="Vercel" height="32" disableZoom />
  <Image src="/logos/stripe.svg" alt="Stripe" height="32" disableZoom />
  <Image src="/logos/github.svg" alt="GitHub" height="32" disableZoom />
  <Image src="/logos/cloudflare.svg" alt="Cloudflare" height="32" disableZoom />
  <Image src="/logos/linear.svg" alt="Linear" height="32" disableZoom />
</Marquee>
```

Images resolve relative to your project root, so `/logos/vercel.svg` maps to `public/logos/vercel.svg`. You can also use relative paths like `./assets/logo.png` from the MDX file's directory.

### Disable zoom inside Marquee

Holocron's `Image` is click-to-zoom by default. Inside a Marquee that is rarely the behavior you want, so pass `disableZoom` on each `Image` to keep it as a plain scrolling logo.

### Dark mode with `dark:invert`

For black logos on a white background, add `className="dark:invert"` so they flip to white in dark mode.

```mdx
<Marquee duration={20} slowOnHover gap={24}>
  <Image src="/logos/acme.svg" alt="ACME" height="32" className="dark:invert" disableZoom />
  <Image src="/logos/stripe.svg" alt="Stripe" height="32" className="dark:invert" disableZoom />
  <Image src="/logos/linear.svg" alt="Linear" height="32" className="dark:invert" disableZoom />
</Marquee>
```

Bare `<img>` tags inside Marquee are also automatically converted to Holocron's `Image` component at build time, preserving `className` and any extra props like `disableZoom`.

## Icon marquee

Combine with `Icon` to scroll through icon sets.

<Marquee duration={15} slowOnHover gap={48}>
  <Icon icon="lucide:rocket" size={28} color="var(--muted-foreground)" />

  <Icon icon="lucide:zap" size={28} color="var(--muted-foreground)" />

  <Icon icon="lucide:shield" size={28} color="var(--muted-foreground)" />

  <Icon icon="lucide:database" size={28} color="var(--muted-foreground)" />

  <Icon icon="lucide:cloud" size={28} color="var(--muted-foreground)" />

  <Icon icon="lucide:code" size={28} color="var(--muted-foreground)" />

  <Icon icon="lucide:globe" size={28} color="var(--muted-foreground)" />

  <Icon icon="lucide:heart" size={28} color="var(--muted-foreground)" />
</Marquee>

```mdx
<Marquee duration={15} slowOnHover gap={48}>
  <Icon icon="lucide:rocket" size={28} color="var(--muted-foreground)" />
  <Icon icon="lucide:zap" size={28} color="var(--muted-foreground)" />
  <Icon icon="lucide:shield" size={28} color="var(--muted-foreground)" />
  <Icon icon="lucide:database" size={28} color="var(--muted-foreground)" />
</Marquee>
```

## Reverse direction

Scroll right instead of left.

<Marquee direction="right" duration={20} gap={48}>
  <Icon icon="lucide:star" size={28} color="var(--muted-foreground)" />

  <Icon icon="lucide:sparkles" size={28} color="var(--muted-foreground)" />

  <Icon icon="lucide:sun" size={28} color="var(--muted-foreground)" />

  <Icon icon="lucide:moon" size={28} color="var(--muted-foreground)" />

  <Icon icon="lucide:cloud" size={28} color="var(--muted-foreground)" />

  <Icon icon="lucide:bolt" size={28} color="var(--muted-foreground)" />
</Marquee>

```mdx
<Marquee direction="right" duration={20}>
  <Icon icon="lucide:star" size={28} />
  <Icon icon="lucide:sparkles" size={28} />
</Marquee>
```

## Pause on hover

Hover to pause the animation.

<Marquee slowOnHover duration={12} gap={48}>
  <Badge color="blue">TypeScript</Badge>
  <Badge color="green">Fast builds</Badge>
  <Badge color="purple">MDX</Badge>
  <Badge color="orange">Vite</Badge>
  <Badge color="red">Deploy</Badge>
  <Badge color="blue">Search</Badge>
  <Badge color="green">Theming</Badge>
  <Badge color="purple">OpenAPI</Badge>
</Marquee>

```mdx
<Marquee slowOnHover duration={12}>
  <Badge color="blue">TypeScript</Badge>
  <Badge color="green">Fast builds</Badge>
  <Badge color="purple">MDX</Badge>
  <Badge color="orange">Vite</Badge>
</Marquee>
```

## No fade

Disable edge fading with `fade={false}`.

<Marquee fade={false} duration={18} gap={48}>
  <Icon icon="lucide:terminal" size={28} color="var(--muted-foreground)" />

  <Icon icon="lucide:monitor" size={28} color="var(--muted-foreground)" />

  <Icon icon="lucide:cpu" size={28} color="var(--muted-foreground)" />

  <Icon icon="lucide:hard-drive" size={28} color="var(--muted-foreground)" />

  <Icon icon="lucide:wifi" size={28} color="var(--muted-foreground)" />

  <Icon icon="lucide:bluetooth" size={28} color="var(--muted-foreground)" />
</Marquee>

## Props

<ResponseField name="duration" type="number" default="20">
  Time in seconds for one full scroll cycle. Controls perceived speed: `duration={10}` scrolls twice as fast as `duration={20}`. The actual pixel speed depends on how many children you have, since more items means more distance to cover in the same time.
</ResponseField>

<ResponseField name="slowOnHover" type="boolean" default="false">
  Pause the animation when the user hovers over the marquee.
</ResponseField>

<ResponseField name="direction" type="&#x22;left&#x22; | &#x22;right&#x22; | &#x22;up&#x22; | &#x22;down&#x22;" default="&#x22;left&#x22;">
  Scroll direction. Use `up` or `down` for vertical marquees.
</ResponseField>

<ResponseField name="fade" type="boolean" default="true">
  Show a fade gradient at the edges.
</ResponseField>

<ResponseField name="fadeAmount" type="number" default="10">
  Percentage of the container used for the fade gradient (0-100).
</ResponseField>

<ResponseField name="gap" type="number" default="24">
  Gap between items in pixels.
</ResponseField>

<ResponseField name="className" type="string">
  Additional CSS classes applied to the outer container.
</ResponseField>

<ResponseField name="children" type="React.ReactNode" required>
  Content to scroll. Each direct child becomes a marquee item.
</ResponseField>


---
title: Mermaid Diagrams
url: "https://holocron.so/docs/components/mermaid-diagrams.md"
description: Use mermaid code fences for diagrams in docs.
---

# Mermaid Diagrams

Holocron supports fenced `mermaid` blocks, which makes them easy to write in plain MDX.

<Mermaid
  chart="flowchart LR
  A[MDX source] --> B[Holocron parser]
  B --> C[Rendered editorial page]"
/>

<Mermaid
  chart="sequenceDiagram
  participant Author
  participant Holocron
  participant Browser
  Author->>Holocron: write docs.json + MDX
  Holocron->>Browser: stream rendered page
  Browser-->>Author: interactive docs UI"
/>

## Holocron differences

* Holocron supports the core authoring flow: fenced mermaid blocks rendered inline on the page.
* Mintlify documents control placement and larger diagram ergonomics more explicitly. Those remain good parity targets if we extend the renderer later.


---
title: Panel
url: "https://holocron.so/docs/components/panel.md"
description: Use a panel as a grouped right-hand supporting surface.
---

# Panel

`Panel` is useful when a page wants a structured side surface instead of the default right-hand flow of ordinary prose.

<Aside full>
  <Panel>
    <Aside>
      <RequestExample>
        ```bash
        pnpm -F website dev
        ```
      </RequestExample>
    </Aside>

    <Aside>
      <ResponseExample>
        ```json
        {
          "url": "/components/panel",
          "status": "rendered"
        }
        ```
      </ResponseExample>
    </Aside>
  </Panel>
</Aside>

This page uses the panel itself as the example so the layout behavior is visible, not just described.

## Holocron differences

* In Holocron, panel behavior is closely tied to the editorial page grid and `Aside` placement.
* Mintlify describes panel behavior at a more abstract layout level; Holocron's docs should stay concrete and show the exact rendered result.


---
title: Prompt
url: "https://holocron.so/docs/components/prompt.md"
description: Present AI prompts and instructions in a framed block.
---

# Prompt

Use `Prompt` to show an instruction block for AI tooling or repeatable workflows.

<Prompt description="Ask Holocron to explain a component difference" icon="sparkles">
  Compare Holocron's `Tabs` implementation with Mintlify's `Tabs` docs and list any unsupported behavior.
</Prompt>

<Prompt description="Internal docs workflow">
  Build the website package, open the component page in light and dark mode, and note any rendering differences.
</Prompt>

## Holocron differences

* Holocron supports the prompt card shape.
* Mintlify also documents extra AI-specific actions; Holocron currently keeps the surface small and centered on the prompt body itself.


---
title: Responses
url: "https://holocron.so/docs/components/responses.md"
description: Document structured response shapes with response fields and nested details.
---

# Responses

`ResponseField` is the response-side companion to `ParamField`.

<ResponseField name="page" type="object" required>
  Top-level page payload returned by the docs runtime.

  <Expandable title="Expand object fields">
    <ResponseField name="slug" type="string" required>
      Canonical route slug.
    </ResponseField>

    <ResponseField name="title" type="string">
      Human-readable page title.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="headings" type="NavHeading[]">
  Heading list used by the client-side table of contents.
</ResponseField>

## Holocron differences

* Holocron's response-field rendering is intentionally compact and works well with nested `Expandable` blocks.
* Mintlify documents additional response formatting knobs that Holocron does not currently expose.


---
title: Steps
url: "https://holocron.so/docs/components/steps.md"
description: Use steps for ordered guides and task flows.
---

# Steps

Use `Steps` and `Step` for short procedures where the order matters.

<Steps>
  <Step title="Install the package" icon="download">
    Add Holocron and its peer dependencies.

    ```bash
    pnpm add @holocron.so/vite react react-dom vite
    ```
  </Step>

  <Step title="Create docs.json" icon="file-json">
    Define the site name and navigation tree.
  </Step>

  <Step title="Write MDX pages" icon="file-text">
    Add content pages that use the same Mintlify-style component names documented on this site.

    * Steps can contain nested prose
    * Lists and code blocks keep their spacing
  </Step>
</Steps>

## Holocron differences

* Holocron supports step titles, optional icons, and arbitrary MDX in the step body.
* Mintlify documents more anchor-oriented behavior around steps. Holocron does not yet expose per-step ids or anchor controls.


---
title: Table of Contents
url: "https://holocron.so/docs/components/table-of-contents.md"
description: "Show a \"On this page\" sidebar with heading links."
---

# Table of Contents

The `TableOfContentsPanel` component renders a vertical list of heading links in the right sidebar with active-state tracking. It highlights the currently visible section as the user scrolls.

```mdx
<Aside full>
  <TableOfContentsPanel />
</Aside>
```

<Aside>
  <Note>
    Wrap `TableOfContentsPanel` in `<Aside full>` so it spans the full page height and stays sticky while scrolling. Without the wrapper, the panel only spans one section.
  </Note>
</Aside>

## Why you might need this

By default, Holocron shows the [AI Assistant](/docs/ai/assistant) widget in the right sidebar. If you prefer a traditional table of contents instead, add `TableOfContentsPanel` to any page. You can also use both: place the AI widget and the TOC panel in the same `<Aside full>` and they stack vertically.

## Automatic headings

When used without props, the component reads the current page's headings automatically. No configuration needed.

```mdx
<Aside full>
  <TableOfContentsPanel />
</Aside>
```

This picks up every `##`, `###`, and deeper heading on the page, indented by depth.

## Sidebar headings are hidden automatically

Normally the left sidebar shows the current page's section headings inline, under the active page entry. When a page renders a `TableOfContentsPanel`, Holocron **detects it at build time** and hides that inline list — the same headings would otherwise appear twice, once in each sidebar.

No configuration is needed. To override the behavior, set the `sidebarToc` frontmatter field:

```mdx
---
$schema: https://holocron.so/frontmatter.json
title: My Page
# true: show sidebar headings even with a TableOfContentsPanel present
# false: hide sidebar headings on any page, panel or not
sidebarToc: true
---
```

Sidebar search still matches and shows headings regardless of this setting, so heading results stay reachable.

## Custom title

Change the label above the heading list with the `title` prop. Defaults to `"On this page"`.

```mdx
<Aside full>
  <TableOfContentsPanel title="Contents" />
</Aside>
```

## Adding it to every page

To show a TOC on every page without repeating the MDX snippet, create a shared snippet file and import it:

```mdx title="snippets/toc.md"
<Aside full>
  <TableOfContentsPanel />
</Aside>
```

Then import it at the top of each page:

```mdx title="getting-started.mdx"
import Toc from '/snippets/toc.md'

<Toc />

## First heading

Content here...
```

See [Local Imports](/docs/create/local-imports) for more on reusable snippets.

## Props

<ResponseField name="title" type="string" default="On this page">
  Label shown above the heading list.
</ResponseField>

<ResponseField name="headings" type="NavHeading[]">
  Custom list of headings to display. When omitted, the component reads the current page's headings automatically. Each entry has `slug`, `text`, and `depth` fields.
</ResponseField>

<ResponseField name="className" type="string">
  Additional CSS class name for the outer `<nav>` element.
</ResponseField>


---
title: Tabs
url: "https://holocron.so/docs/components/tabs.md"
description: Split related content into tabbed panels.
---

# Tabs

Use `Tabs` and `Tab` when multiple variants of the same content should share one footprint.

<Tabs>
  <Tab title="npm" icon="package">
    ```bash
    npm install @holocron.so/vite react react-dom vite
    ```
  </Tab>

  <Tab title="pnpm" icon="package-check">
    ```bash
    pnpm add @holocron.so/vite react react-dom vite
    ```
  </Tab>
</Tabs>

<Tabs defaultTabIndex={1}>
  <Tab title="Overview">
    Tabs can hold prose and nested components.
  </Tab>

  <Tab title="Examples">
    <CardGroup cols={2}>
      <Card title="Cards inside tabs" icon="square-stack">
        Useful for docs landing pages.
      </Card>

      <Card title="Mixed content" icon="wrap-text">
        Holocron handles nested MDX blocks here too.
      </Card>
    </CardGroup>
  </Tab>
</Tabs>

## Supported props

* `Tabs` supports `defaultTabIndex`, `sync`, and nested `Tab` children.
* `Tab` supports `title`, `value`, and `icon`.

### Default tab

```mdx
<Tabs defaultTabIndex={1}>
  <Tab title='First'>...</Tab>
  <Tab title='Second'>Opens by default.</Tab>
</Tabs>
```

### Sync by title

When **`sync`** is true, selecting a tab publishes its **title**. Other synced
`<Tabs>` on the site that include the same title switch to it. The choice is
stored in `localStorage` so it survives navigation.

```mdx
<Tabs sync>
  <Tab title='TypeScript'>...</Tab>
  <Tab title='Python'>...</Tab>
</Tabs>
```

OpenAPI **Request example** panels enable `sync` by default, so picking
TypeScript on one endpoint keeps TypeScript on the next. Use `sync={false}` to
opt out of a single group.

## Holocron differences

* Sync matches by **tab title** (exact, then case-insensitive), same idea as Mintlify.
* Preference is hydration-safe: the server always renders `defaultTabIndex`; the
  stored title applies after the client hydrates.


---
title: Tiles
url: "https://holocron.so/docs/components/tiles.md"
description: Create visual preview links with titles and descriptions.
---

# Tiles

Use `Tile` for page hubs where a visual preview helps the reader understand what they are clicking into.

<Columns cols={2}>
  <Tile href="/docs/components/accordions" title="Accordions" description="Disclosure patterns and grouped accordions">
    <div className="rounded-md bg-muted p-6 text-center">Accordion preview</div>
  </Tile>

  <Tile href="/docs/components/color" title="Color" description="Palette and token documentation">
    <div className="rounded-md bg-muted p-6 text-center">Color preview</div>
  </Tile>
</Columns>

## Holocron differences

* Holocron supports the core `Tile` shape: `href`, `title`, `description`, and arbitrary preview children.
* Mintlify often uses image-heavy previews here. Holocron's docs keep the examples lightweight and local by default.


---
title: Tooltips
url: "https://holocron.so/docs/components/tooltips.md"
description: Add compact inline explanations without interrupting the page flow.
---

# Tooltips

Tooltips are useful for glossary-like inline explanations.

Hover over this term: <Tooltip tip="A prebuilt MDX component surface with cards, callouts, code blocks, and more.">component library</Tooltip>.

Tooltips can also include a small headline and a call to action: <Tooltip tip="Browse all available components in this docs site." headline="Holocron docs workflow" cta="View components" href="/docs/components">component index</Tooltip>.

## Holocron differences

* Holocron supports inline tooltip content plus optional headline and CTA link.
* This is a hover-driven implementation; broader interaction patterns documented by Mintlify are not all mirrored yet.


---
title: Tree
url: "https://holocron.so/docs/components/tree.md"
description: Render nested file and folder hierarchies.
---

# Tree

Use `Tree` to show file structures, generated outputs, or nested conceptual hierarchies.

<Tree>
  <Tree.Folder name="website" defaultOpen>
    <Tree.File name="docs.json" />

    <Tree.File name="index.mdx" />

    <Tree.Folder name="components" defaultOpen>
      <Tree.File name="accordions.mdx" />

      <Tree.File name="tabs.mdx" />

      <Tree.File name="view.mdx" />
    </Tree.Folder>
  </Tree.Folder>

  <Tree.Folder name="vite" openable={false}>
    <Tree.File name="src/" />
  </Tree.Folder>
</Tree>

## Holocron differences

* Holocron supports nested folders, `defaultOpen`, and non-openable folders.
* Mintlify documents keyboard behavior more explicitly; Holocron's current tree is a compact authoring primitive first.


---
title: Update
url: "https://holocron.so/docs/components/update.md"
description: Publish compact changelog and release-note style entries.
---

# Update

Use `Update` for change logs, release notes, and notable docs updates.

<Update label="2026-04-11" description="website package added" tags={['docs', 'release']}>
  Added a dedicated Holocron docs website package based on the Mintlify component docs IA.

  * New homepage for Holocron itself
  * New component docs section under `/components`
  * Extra compatibility notes where Holocron differs from Mintlify

  ```bash
  pnpm -F website build
  ```
</Update>

## Holocron differences

* Holocron supports the core release-note card with label, description, tags, and rich children.
* Mintlify documents RSS-specific authoring details. Holocron does not currently expose that full workflow.


---
title: View
url: "https://holocron.so/docs/components/view.md"
description: Group alternate views of related content.
---

# View

Mintlify uses `View` for switchable content presentations. In Holocron today, `View` renders as a labeled content section rather than a global multi-view controller.

<View title="Vite config" icon="zap">
  ```ts
  import { defineConfig } from 'vite'
  import { holocron } from '@holocron.so/vite'

  export default defineConfig({
    plugins: [holocron()],
  })
  ```
</View>

<View title="docs.json" icon="file-json">
  ```json
  {
    "name": "Holocron",
    "navigation": [{ "group": "Overview", "pages": ["index"] }]
  }
  ```
</View>

## Holocron differences

* Holocron supports `View` as a framed, labeled content block.
* Mintlify documents a more dynamic multi-view experience with TOC interactions. Holocron does **not** currently implement that behavior.


---
title: Visibility
url: "https://holocron.so/docs/components/visibility.md"
description: Show different content to humans on the web and AI agents in markdown output.
---

# Visibility

Show different content to humans reading your docs site versus AI agents processing markdown output (`.md` URLs and `docs.zip`).

Content marked `for="humans"` renders on the web page but is stripped from agent markdown. Content marked `for="agents"` is hidden on the page but included in agent markdown.

## Example

```mdx
<Visibility for="humans">
Click the **Get started** button in the top-right corner to create your account.
</Visibility>

<Visibility for="agents">
To create an account, call `POST /v1/accounts` with a valid email address.
</Visibility>
```

On your published site, the first block renders and the second is hidden. When an AI agent fetches the `.md` version of the page, the opposite applies.

## Props

<ResponseField name="for" type="&#x22;humans&#x22; | &#x22;agents&#x22;">
  Which audience sees the content. Defaults to `"humans"` if omitted.
</ResponseField>

## How it works

Holocron already serves every page as raw markdown at `<page-url>.md` and redirects known AI agents automatically. The `Visibility` component hooks into this:

* **Web rendering**: `for="agents"` returns nothing, `for="humans"` (or no prop) renders children normally.
* **Markdown output**: `for="humans"` blocks are stripped, `for="agents"` blocks are unwrapped so only their inner content remains.

<Tip>
  Use this to give AI agents API-oriented instructions (endpoints, code snippets) while showing humans UI-oriented instructions (screenshots, button locations).
</Tip>


---
title: Maintain documentation from source changes
url: "https://holocron.so/maintain.md"
description: "Rerun page generation prompts when sources change. Run it from a parent agent, or let GitHub Actions open the pull request."
---

import { HeroSection } from '../../components/hero-section.tsx'

<Above>
  <HeroSection lines={['docs that maintain', 'themselves']} />
</Above>

Holocron Maintain watches every documentation page that includes a **`prompt` field in its frontmatter**. The prompt records how the page was generated and identifies its source files, folders, and URLs.

When a referenced source changes, Maintain gives the **current page, its generation prompt, and the relevant source changes** to OpenCode. The page remains unchanged when the source change does not affect its content.

There are **two ways to run** it. Maintain never opens a pull request outside GitHub Actions.

```diagram
                         holocron maintain
                                │
                 ┌──────────────┴──────────────┐
                 v                             v
          parent agent                   GitHub Actions
          --since origin/main            no args on push
                 │                             │
                 v                             v
          edit MDX only                  edit MDX, then PR
          parent opens the PR            holocron/maintain-<timestamp>
```

Use the **parent-agent** path in a coding session or custom CI. Use **GitHub Actions** when you want Maintain to open the pull request itself. See [GitHub Actions](/maintain/github-actions) for the no-args push workflow.

## Generate a maintainable page

The page-level **`prompt`** is the original instruction used to generate the page. Use **`@/path`** for files and folders from the **repository root**. Use `@./path` or `@../path` when the source sits next to the MDX page. Prefix remote sources with **`@https://`**. Bare URLs in prose are not references.

```mdx
---
$schema: https://holocron.so/frontmatter.json
title: Authenticate API requests
sidebarTitle: Authentication
description: Authenticate requests with sessions, API keys, and GitHub Actions OIDC.
prompt: |
  Write the authentication guide from @/src/auth/ and
  @/src/middleware/session.ts.
  Use @https://github.com/example/project/releases for recent behavior changes.
  Explain sessions, API keys, and GitHub Actions OIDC authentication.
  Include a complete TypeScript example for every method.
---

# Authentication

Use a session or API key to authenticate API requests.
```

```diagram
  repository root
       │
       ├── src/auth/                         <── @/src/auth/
       ├── src/middleware/session.ts         <── @/src/middleware/session.ts
       └── website/src/pages/docs/auth.mdx
                   │
                   └── @./setup.mdx            relative to this page
```

**`@/`** always starts at the git repository root, so the path stays valid if you move the MDX file. **`@./`** and **`@../`** start at the MDX file. **`@https://`** marks a remote source. A file reference selects the page when that file changes. A folder reference selects it when any tracked file inside that folder changes, including files inside a git submodule. Use the parent-root path for submodule sources, for example `@/template/src/index.mdx`. GitHub release events and explicit routine runs can select matching `@https://` URL references.

The **Holocron skill adds this field by default** when an AI coding agent creates a new MDX page. It records the real sources used to produce the page, so future agents can reproduce and update the content instead of guessing its intent. See [skill discovery](/docs/ai/skill-discovery) for agent setup.

## Parent agent flow

Run Maintain from a **parent agent** that already owns git and GitHub. The command only updates selected MDX files. It does not create a branch, commit, or pull request. The parent agent reviews the working tree and opens a PR with whatever process that repo already uses.

```bash
npx -y "@holocron.so/cli" maintain --since origin/main
```

`--since` compares the **merge base** of that ref with `HEAD`. Pages whose `@/` sources sit in that range are selected.

With **no flags**, Maintain diffs the working tree against `HEAD`. That is useful when the parent already changed source files in the same session.

Use **`--dry-run`** to inspect the selection without authentication or a model call.

```bash
npx -y "@holocron.so/cli" maintain --since origin/main --dry-run
```

<Aside>
  <Tip>
    Keep git write tools on the **parent**. Maintain's OpenCode session can only edit the selected MDX pages.
  </Tip>
</Aside>

## Run a routine review

Use **`--all` with a run prompt** for work that is not tied to one changed source, such as grammar, SEO, links, or style checks.

```bash
npx -y "@holocron.so/cli" maintain --all \
  --prompt "Audit every page for weak titles and descriptions."
```

Long instructions can live in a versioned Markdown file.

```bash
npx -y "@holocron.so/cli" maintain --all \
  --prompt-file .holocron/prompts/weekly-review.md
```

The run prompt is **temporary**. It does not replace page generation prompts.

## OpenCode execution

Maintain uses **one OpenCode session** for the complete run. The main agent groups independent pages by source and delegates those groups as parallel tasks. Holocron then validates every changed MDX file.

**GitHub Actions** adds publish instructions to the session prompt, not the system prompt, so tasks never try to open a pull request. The parent session creates `holocron/maintain-<timestamp>` and runs `gh pr create` only when MDX files changed. The [GitHub Actions](/maintain/github-actions) page covers the no-args push range.

**Parent-agent** runs omit that prompt block. Git commit, push, and `gh` stay denied. The parent continues with its own PR setup after the command exits.

## Models

By default Maintain uses a **Holocron-hosted model**. That call goes through Holocron and is **billed to the site's Pro subscription**. You do not set an Anthropic or OpenAI key for this path.

| Hosted id           | Notes    |
| ------------------- | -------- |
| `deepseek-v4-flash` | Default  |
| `glm-5.3-flash`     | Cheapest |

```bash
npx -y "@holocron.so/cli" maintain --since origin/main
npx -y "@holocron.so/cli" maintain --model glm-5.3-flash
```

Pass **`--model provider/model`** to use your own OpenCode provider instead. Holocron does not create a run, does not bill credits, and does not need Pro. OpenCode reads the provider key from the environment, or from `opencode auth login` (`/connect` inside the OpenCode TUI).

```bash
npx -y "@holocron.so/cli" maintain --model anthropic/claude-sonnet-4-5
```

```diagram
  holocron maintain
         │
         ├── no --model / glm-5.3-flash ──> Holocron-hosted model (Pro bill)
         │
         └── --model anthropic/claude-sonnet-4-5
                   │
                   └──> OpenCode provider + your ANTHROPIC_API_KEY
```

<Aside>
  <Info>
    See the OpenCode **[providers](https://opencode.ai/docs/providers/)** page for the env vars each backend accepts (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, AWS keys, and the rest). Model ids use the **`provider/model`** form from the OpenCode **[models](https://opencode.ai/docs/models/)** page.
  </Info>
</Aside>

## Authentication and billing

**Holocron-hosted models** need **Holocron Pro**. GitHub Actions authenticates through OIDC with `id-token: write`, so the workflow does not need a stored Holocron key.

Other CI systems can set **`HOLOCRON_KEY`**. Local runs use the existing `holocron login` session. Pass `--project` only when the local account has multiple projects.

**`--model provider/model`** skips Holocron auth. Set the provider key from the [OpenCode providers](https://opencode.ai/docs/providers/) docs instead.

## Options

| Option                 | Purpose                                                                |
| ---------------------- | ---------------------------------------------------------------------- |
| `--all`                | Review all prompted pages, or every page when used with a run prompt.  |
| `--since <ref>`        | Detect source changes between the ref merge base and `HEAD`.           |
| `--prompt <text>`      | Add temporary instructions for this run.                               |
| `--prompt-file <path>` | Read temporary instructions from a Markdown file.                      |
| `--dry-run`            | Report selected pages without a model call.                            |
| `--model <id>`         | Holocron-hosted model, or `provider/model` for your own OpenCode keys. |
| `--project <id>`       | Select a project for a local session with multiple projects.           |

See [GitHub Actions](/maintain/github-actions) for the no-args push range and the workflow that opens the pull request.


---
title: Run documentation maintenance in GitHub Actions
url: "https://holocron.so/maintain/github-actions.md"
description: "No-args Maintain on a push diffs that push's before and after SHAs, then OpenCode opens a pull request when MDX pages change."
---

This is the **GitHub Actions** path. Maintain reads the push event, updates matching MDX pages, and opens a pull request. For a parent agent that should open the PR itself, use the [parent-agent flow](/maintain/index#parent-agent-flow).

GitHub Actions provides the **Git range, repository identity, OIDC authentication, and GitHub token**. No stored Holocron key is needed.

On a GitHub Actions run, Maintain adds **publish instructions to the session prompt**. OpenCode updates the selected MDX pages, then opens a pull request when those files changed. Local and parent-agent runs do not get those instructions, so they never create a branch or pull request.

```diagram
  push to main
        │
        v
  holocron maintain
        │
        v
  OpenCode updates MDX pages
        │
        ├── no MDX changes ──> stop
        └── MDX files changed
                │
                v
         branch holocron/maintain-<timestamp>
                │
                v
         gh pr create into main
```

Keep **`GITHUB_TOKEN`** on the Maintain step so OpenCode can push the new branch and open the pull request. Leave checkout credentials enabled. Do not set `persist-credentials: false`.

## After every main-branch push

Run **`holocron maintain` with no args**. GitHub writes the push payload to **`GITHUB_EVENT_PATH`**. Maintain reads `before` (the branch tip before this push) and `after` (the new tip, this checkout), then runs `git diff before..after`.

That range is the **whole push**. Five commits in one push all count. It is not `HEAD~1`.

```diagram
  git push (one or more commits)
           │
           v
  GITHUB_EVENT_PATH
    before  = old tip of the branch
    after   = new tip
           │
           v
  git diff before..after
           │
           v
  pages whose @/ sources sit in that diff
```

Checkout needs **`fetch-depth: 0`** so those SHAs exist locally. A new-branch push has `before` all zeros. Maintain then uses `git diff-tree --root` on `after`.

If MDX files change, OpenCode creates **`holocron/maintain-<timestamp>`** and opens one pull request into `main`. It never commits on `main` or on any other existing branch.

```yaml
name: Maintain documentation

on:
  push:
    branches: [main]

permissions:
  contents: write
  pull-requests: write
  id-token: write

jobs:
  maintain:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - run: npx -y "@holocron.so/cli" maintain
        env:
          GITHUB_TOKEN: ${{ github.token }}
```

`contents: write` lets OpenCode create the maintain branch. `pull-requests: write` lets it open the pull request. `id-token: write` authenticates Holocron over OIDC.

Protect `main` with a ruleset that requires a pull request and does not let GitHub Actions bypass it. The token can still create `holocron/maintain-*` branches.

<Aside>
  <Warning>
    GitHub blocks pull requests from Actions until you enable **Allow GitHub Actions to create and approve pull requests** in Settings, Actions, General, Workflow permissions. See the [GitHub docs](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-github-actions-settings-for-a-repository#preventing-github-actions-from-creating-or-approving-pull-requests).
  </Warning>
</Aside>

## Scheduled maintenance

Use a schedule for **routine audits** that do not depend on a specific source change.

```yaml
name: Weekly documentation review

on:
  schedule:
    - cron: "0 9 * * 1"
  workflow_dispatch:

permissions:
  contents: write
  pull-requests: write
  id-token: write

jobs:
  maintain:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - run: |
          npx -y "@holocron.so/cli" maintain \
            --all \
            --prompt-file .holocron/prompts/weekly-review.md
        env:
          GITHUB_TOKEN: ${{ github.token }}
```

## Do not cancel active runs

Do not set **`cancel-in-progress: true`** for push maintenance. A later push compares only its own `before` and `after` states, so cancelling the preceding run can leave its source changes unreviewed.

A run that changes no MDX files creates no branch and no pull request.

## Use your own model

The workflows above use the **Holocron-hosted model** and bill the project's Pro subscription. OIDC is enough. No provider API key.

To run **Anthropic, OpenAI, or any other OpenCode provider**, pass `--model provider/model` and set that provider's env var. Holocron auth is not used. See the OpenCode **[providers](https://opencode.ai/docs/providers/)** page for the supported keys.

Use a **dedicated provider key**. The OpenCode session can run `git` and `gh`, and it can read the environment.

```yaml
- run: npx -y "@holocron.so/cli" maintain --model anthropic/claude-sonnet-4-5
  env:
    ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
    GITHUB_TOKEN: ${{ github.token }}
```

You can drop `id-token: write` when the Holocron-hosted model is not used. Keep `contents: write` and `pull-requests: write` so OpenCode can still open the pull request.


---
title: Pricing
url: "https://holocron.so/docs/pricing.md"
description: Holocron is free to try and $99/month per site for the full AI assistant and unlimited preview deployments.
---

# Pricing

Holocron is **free to start**. Create a site, write your docs, and get a built-in **AI chat assistant** out of the box. When you need the full model and unlimited preview deployments, **Holocron Pro** has you covered. [Subscribe from your dashboard](/dashboard).

## Plans

<Aside>
  <Info>
    Subscriptions are **per site**. You only pay for sites you actively maintain.
  </Info>
</Aside>

|                                 | **Free**           | **Pro**                             |
| ------------------------------- | ------------------ | ----------------------------------- |
| Hosted AI chat                  | Trial only         | Full model, 50,000 credits / month  |
| Holocron Maintain               | —                  | Source-aware documentation updates  |
| Preview deployments             | 1 trial deployment | Unlimited, every branch and PR      |
| Custom domains                  | —                  | Point your own domain at your docs  |
| Subpath hosting (`--base-path`) | —                  | Host docs at `/docs` on your domain |
| Analytics                       | —                  | Coming soon                         |
| Price                           | $0                 | **$99 / month**                     |

The **yearly plan** is **$990 / year**, two months free compared to paying monthly.

## What Pro unlocks

### Full AI chat assistant

The built-in **AI assistant** answers questions about your docs using your content. Free sites include a trial so you can see the chat in action, but it uses a temporary model and shows an upgrade notice. **Pro** sites get the full model with no notice and **50,000 credits per month**. Credits are tracked per site, so one project hitting its limit never affects another.

### Holocron Maintain

**Holocron Maintain** reruns versioned page generation prompts when their referenced source files change. It can also run scheduled grammar, SEO, link, translation, and style reviews. GitHub Actions authenticates through OIDC, so CI workflows do not store a Holocron key.

### Unlimited preview deployments

On the **Free** plan you get a single trial deployment so you can see your site live. **Pro** removes the limit: every push to your default branch publishes a new version, and every branch or pull request gets its **own unique deployment URL** via GitHub Actions OIDC. Review docs changes in context before they reach production, the same way you review code.

<Aside>
  <Tip>
    Deployments are authenticated via **GitHub Actions OIDC**, no API keys needed for CI.
  </Tip>
</Aside>

### Custom domains

Serve your docs on **your own domain** like `docs.mycompany.com`. Add a CNAME record pointing to `cname.holocron.so`, register the domain in your dashboard settings, and Holocron handles SSL certificates automatically. Custom domains work alongside subpath hosting, so you can choose the URL structure that fits your brand.

### Subpath hosting

Deploy your docs at a **subpath on your own domain**, like `yoursite.com/docs`, instead of a separate subdomain. Google indexes the content under your main domain, which strengthens your site's overall SEO authority. Users stay on one domain with a seamless experience, and you keep full control over the URL structure.

Deploy with a single flag:

```bash
npx -y "@holocron.so/cli" deploy --base-path /docs
```

Then add a [reverse proxy or rewrite](/docs/deploy/base-path) in your framework to forward `/docs/*` to the deployed URL. Guides are available for Next.js, Vercel, Cloudflare Workers, Nginx, and Express.

## Subscribe

Manage billing from the **Billing** tab of your project in the dashboard. Pick monthly or yearly, and switch or cancel any time through the Stripe billing portal.

<Aside>
  <Note>
    Billing runs on **Stripe**. Update payment, download invoices, or cancel any time.
  </Note>
</Aside>

[Open the dashboard](/dashboard) to subscribe.


---
title: Changelog
url: "https://holocron.so/changelog.md"
description: Release notes for Changelog.
---

import _ChangelogIntro from './changelog/intro.mdx'

<Aside full>
  <Note>
    This changelog is generated automatically from the [GitHub releases](https://github.com/remorses/holocron/releases) page.
  </Note>
</Aside>

import { HeroSection } from '../components/hero-section.tsx'

<Above>
  <HeroSection />
</Above>

What's new in Holocron. All releases are published on [GitHub](https://github.com/remorses/holocron/releases).

<Update id={"@holocron.so/vite@0.35.1"} label={"Sep 7, 2026"}>
  ## @holocron.so/vite\@0.35.1

  1. **Remove `<Aside wide>`** — the right rail stays a **fixed** width. Extra viewport space is gap, not a growing aside.

     API pages already match Mintlify. Wrap `RequestExample` and `ResponseExample` in `<Aside full>`. That bumps the rail to **460px**:

     ```mdx
     <Aside full>
     <RequestExample>
     curl example
     </RequestExample>
     </Aside>
     ```

     Use `width` for any other size:

     ```mdx
     <Aside full width={560}>
     <Note>
     This rail is 560px.
     </Note>
     </Aside>
     ```

     `RequestExample` and `ResponseExample` already use 460px. You only need `width` when you want a different size.
</Update>

<Update id={"@holocron.so/vite@0.35.0"} label={"Sep 7, 2026"}>
  ## @holocron.so/vite\@0.35.0

  1. **`<Aside wide>`** — the right rail can take leftover viewport space, like API reference pages. The middle content column stays capped at **720px**. Extra width goes to the aside instead of becoming gap:

     ```mdx
     <Aside wide>
     <Note>
     This rail grows into leftover space on the right.
     </Note>
     </Aside>
     ```

     Use `width` for a fixed pixel size, or combine it with `wide` as the minimum:

     ```mdx
     <Aside wide width={480}>
     <Panel>
       Large examples, diagrams, or embeds.
     </Panel>
     </Aside>
     ```

     `RequestExample` and `ResponseExample` already bump the rail to **460px**. `wide` still works with them: 460px is the minimum, then the rail grows. Combine `wide` with `full` to keep that rail sticky while you scroll.

  2. **Local SVG file icons** — root-absolute paths like `/icons/vercel.svg` and relative paths like `./icons/vercel.svg` now render as the same sized `currentColor` SVG as Lucide icons, instead of a plain `<img>`. Holocron looks in `public/`, then the project root. A missing file fails the production build. Remote `https://` icons still render as images:

     ```mdx
     ---
     title: Vercel deployments
     icon: /icons/vercel.svg
     ---
     ```

     ```json
     {
       "group": "Integrations",
       "icon": "/icons/slack.svg",
       "pages": ["integrations/slack"]
     }
     ```

     The SVG should use **`currentColor`** for `fill` and `stroke`. A `../` path is rejected and fails the production build.

  3. **Keep the docs page usable while AI chat is open.** The chat drawer no longer locks page scroll, no longer covers the site with a click-catcher, and no longer closes when you click a page link. Close it with the × button. You can keep reading, scrolling, and navigating while the conversation stays on screen.

  4. **Keep one H1 on landing pages.** Pages with `<Above>` no longer get an extra injected title heading. Extra H1s after the first are demoted to H2, so a hero heading stays the main title and body sections stay H2.

     Holocron also warns when frontmatter YAML looks nested because of an unquoted `:`, or when a parsed key contains a space and `:`. Quote those strings.

  5. **Keep Ask AI sticky on pages whose only asides sit in the intro.** If later headings have no asides of their own, Holocron now injects a page-spanning `<Aside full>` and collects those intro callouts into it. Ask AI no longer unsticks after the first section.

     Also restore vertical padding on left-sidebar TOC heading rows.
</Update>

<Update id={"@holocron.so/vite@0.34.0"} label={"Sep 5, 2026"}>
  ## @holocron.so/vite\@0.34.0

  1. **Built-in `/github` shortcut** — if the site config links to GitHub from the navbar, navigation, logo, or footer, Holocron adds a temporary redirect from `/github` to that URL:

     ```text
     /github → https://github.com/owner/repository
     ```

     An authored `github.mdx` page or an explicit redirect whose source matches `/github` takes priority.

  2. **`assistant.supportEmail` in `docs.json`** — the AI chat can send people to a human when the docs are not enough:

     ```json
     {
       "assistant": {
         "supportEmail": "support@example.com"
       }
     }
     ```

     When this is set, the assistant answers from the docs first. It only shares that address if the user wants to talk to a human, or if the docs cannot answer the question. It does not invent other support channels.

  3. **Clickable folder pages** — a navigation group with `root` is now a real page, not only a label:

     ```json
     {
       "group": "Guides",
       "root": "guides/index",
       "pages": ["guides/setup", "guides/deploy"]
     }
     ```

     The folder label opens the root page. A separate chevron expands and collapses its children. Root pages participate in search, previous/next navigation, sitemap and LLM output, internal-link validation, version ownership, and hidden-page filtering. Version matching also includes `group.root` hrefs, so nested docs in `navigation.versions` stay on the right language.

  4. **Compact layout defaults Ask AI to the floating pill** — `layout.mode: "compact"` in `docs.json` no longer needs `assistant.display`. Compact removes the right aside, so Holocron shows the bottom-center pill unless you set `"display": "sidebar"` to opt out. The pill no longer leaves its background behind after the chat panel opens.

  5. **Compact sidebar is 12px** — compact mode sets `--sidebar-font-size` to 12px at every breakpoint. Default pages stay at 13px (14px at `xl`). Override the token on `:root` to change the size globally.

  6. **Ask AI stays at the top of the right sidebar** on pages with no asides, and on pages with a single `<Aside full>`. The widget uses a page-spanning `<Aside full>` in those cases, so it stays aligned with the top of the page instead of unsticking with the first heading. Pages with other per-section asides are unchanged.

  7. **Untitled code fences have no header** — Holocron no longer uses the fence language as a title. Pass an explicit title when you want a label:

     ````md
     ```ts filename.ts
     export const n = 1
     ```
     ````

  8. **Richer bash highlighting** — bash blocks color the command at the start of each statement, plus npm-style packages like `@scope/cli`. Docs commands such as `npx`, `wrangler`, and `pi` now highlight as commands. Arguments that share a Unix name, such as `file`, stay arguments:

     ```bash
     npx @subrouter/cli login anthropic
     cat file | wrangler deploy
     ```

  9. **Expandable panels use the page background** instead of the elevated card surface. OpenAPI schema and response expandables match the surrounding page.

  10. **Chat tool calls show only the summary label.** Completed tools no longer expand with a Show more button or dump raw tool output. The description line stays. Errors still render.

  11. **`Prev Page` and `Next Page`** footer labels are capitalized consistently.

  12. **Duplicate heading titles get unique HTML ids.** Two headings named Accounts used to both render as `id="accounts"`. Heading ids now use GithubSlugger suffixes, same as the TOC, so the sidebar highlights the section you are in.

  13. **Left sidebar active heading tracks the sticky header line** (`scroll-margin-top`) instead of a fixed 50px offset. Hash links park a heading on that line, so the previous heading no longer stays active while you scroll.
</Update>

<Update id={"@holocron.so/cli@0.22.1"} label={"Sep 5, 2026"}>
  ## @holocron.so/cli\@0.22.1

  1. **Fix `holocron maintain --model` examples and error text.** The BYOK example is now a real OpenCode id, `anthropic/claude-sonnet-4-5`. Failed OpenCode calls print the provider error instead of always saying the API key is missing. Unknown hosted ids hint at the `provider/model` form. Docs name the hosted models (`deepseek-v4-flash` default, `glm-5.3-flash`) and point at `opencode auth login` for keys:

     ```bash
     npx -y "@holocron.so/cli" maintain --model glm-5.3-flash
     npx -y "@holocron.so/cli" maintain --model anthropic/claude-sonnet-4-5
     ```
</Update>

<Update id={"@holocron.so/vite@0.33.1"} label={"Aug 29, 2026"}>
  ## @holocron.so/vite\@0.33.1

  1. **Compact layout never opens a right aside** — `layout.mode: "compact"` used to promote the page back to the full three-column layout when it saw an `<Aside>`. That re-injected the sidebar Ask AI widget.

     Compact now always wins. Authored asides and API examples render in the main column. The sidebar Ask AI widget stays hidden.
</Update>

<Update id={"@holocron.so/vite@0.33.0"} label={"Aug 29, 2026"}>
  ## @holocron.so/vite\@0.33.0

  1. **Compact page mode** — keep the left navigation, drop the optional right aside, and narrow the page frame without changing the reading column width.

     Set it for the full site in `docs.json`:

     ```json
     {
       "layout": {
         "mode": "compact"
       },
       "assistant": {
         "display": "floating"
       }
     }
     ```

     Or set `mode: "compact"` in page frontmatter. Pages with authored asides, table-of-contents panels, or generated API examples keep their required right rail. Compact hides the default sidebar assistant, so pair it with `assistant.display: "floating"` if you still want Ask AI.

  2. **Floating Ask AI pill** — `assistant.display` in `docs.json` can use the same bottom-center chat pill as the embeddable ChatWidget:

     ```json
     {
       "assistant": {
         "display": "floating"
       }
     }
     ```

     `sidebar` (default) keeps the Ask AI widget in the right aside. `floating` hides that widget and the mobile Ask AI button, and shows the bottom-center textarea pill that morphs into the chat drawer.

  3. **Locale-aware version navigation** with `navigation.versions[].lang`:

     ```json
     {
       "navigation": {
         "versions": [
           { "version": "English", "lang": "en", "pages": ["en/index"] },
           { "version": "Nederlands", "lang": "nl", "pages": ["nl/index"] }
         ]
       }
     }
     ```

     Holocron sets the document `lang` from the version that owns the current page. The tab bar, sidebar, search, and previous/next navigation use only that version's tree.

  4. **`prompt` frontmatter for Holocron Maintain** — pages can record the source files, folders, and URLs used to generate them:

     ```yaml
     ---
     $schema: https://holocron.so/frontmatter.json
     prompt: |
       Write the authentication guide from @/src/auth/.
       Use @https://github.com/example/project/releases for recent behavior.
     ---
     ```

     `holocron maintain` in `@holocron.so/cli` reruns those prompts when the referenced sources change.

  5. **`pageCache` plugin option** — set `pageCache: false` to skip reuse of processed navigation and MDX during Holocron development. Image metadata still caches. The default remains `true` for published sites:

     ```ts
     import { holocron } from '@holocron.so/vite/vite'

     export default defineConfig({
       plugins: [holocron({ pageCache: false })],
     })
     ```

  6. **Report every invalid MDX page** through a typed `HolocronDataGenerationError` instead of stopping at the first parser failure.

     Multi-tenant deploy pipelines can import the narrow data API and inspect every page failure without parsing error messages:

     ```ts
     import {
       generateHolocronData,
       isHolocronDataGenerationError,
     } from '@holocron.so/vite/data'

     try {
       await generateHolocronData({ config, slugs, getMdxSource })
     } catch (error) {
       if (!isHolocronDataGenerationError(error)) throw error

       for (const { slug, error: parseError } of error.pageErrors) {
         console.error(slug, parseError.line, parseError.reason)
       }
     }
     ```

     Each page is parsed once. Each parse error includes a stable code, source location, reason, code frame, and raw MDX source.

  7. **Previous/next links move to the footer** — they now sit next to **Powered by Holocron**, with compact **Prev page** / **Next page** labels, chevrons, and tooltips for the target title. The sidebar copy action reads **Copy page as Markdown**. The Ask AI widget stays the full 230px aside width. Short pages pin the footer to the bottom of the viewport instead of leaving a gap below it.

  8. **Markdown tables bleed into the right content gap** the same way fenced code blocks do, so wide tables use the space before the sidebar before they start scrolling.

  9. **Code block titles match the code font size** — filename and language labels above fenced blocks now use `--type-code-size`.

  10. **Softer scrollbar thumbs** in the page and chat widget. Hover no longer jumps to a bright opaque thumb.

  11. **Sidebar page icons align with the first line** of wrapped labels instead of centering against the full multi-line label. Table of contents labels stay at full opacity instead of muting outside the active section.

  12. **Fix markdown images, nested links, and callout icons** — images keep author `width`, `height`, and `style` separate from placeholder dimensions, and responsive height follows the constrained frame so `object-fit: contain` does not leave blank bands. Card, Tile, and linked Badge content can contain valid interactive links without nested anchors. Unknown-language code blocks keep their controls, and custom callout icons fall back cleanly.

  13. **Fix page titles, encoded routes, and sitemaps** for large migrated sites. One frontmatter H1 is generated when MDX starts with a callout or has no body heading; it is skipped when the body already starts with a heading. Percent-encoded apostrophes and parentheses resolve through one canonical route. Sitemap entries come from the same valid page manifest as HTML and `.md` routes. Missing root-relative icon files are validated, and development recovers loader data after RSC program reloads instead of returning a transient 500.
</Update>

<Update id={"@holocron.so/cli@0.22.0"} label={"Aug 29, 2026"}>
  ## @holocron.so/cli\@0.22.0

  1. **New `holocron maintain` command** — keep documentation in sync with the source files, folders, and URLs that generated each page.

     Add a generation prompt to page frontmatter:

     ```yaml
     ---
     $schema: https://holocron.so/frontmatter.json
     prompt: |
       Write the authentication guide from @/src/auth/.
       Use @https://github.com/example/project/releases for recent behavior.
       Explain sessions, API keys, and GitHub Actions OIDC.
     ---
     ```

     `holocron maintain` finds pages whose referenced sources changed, runs one OpenCode session, and validates the resulting MDX. `@/path` refs resolve from the repo root, including files inside git submodules. `@https://` refs match changed remote URLs.

     ```bash
     npx -y "@holocron.so/cli" maintain --since origin/main
     npx -y "@holocron.so/cli" maintain --since origin/main --dry-run
     npx -y "@holocron.so/cli" maintain --all --prompt-file .holocron/prompts/weekly-review.md
     ```

     By default Maintain uses a **Holocron-hosted model** and bills the site's Pro subscription. Pass a hosted id such as `glm-5.3-flash`, or `provider/model` to use your own OpenCode keys with no Holocron auth or credits:

     ```bash
     npx -y "@holocron.so/cli" maintain --model glm-5.3-flash
     npx -y "@holocron.so/cli" maintain --model anthropic/claude-sonnet-4
     ```

     In **GitHub Actions**, no-args `holocron maintain` diffs the whole push from `GITHUB_EVENT_PATH`. The session prompt tells OpenCode to create `holocron/maintain-<timestamp>` and open a pull request when MDX files changed. It never updates `main` or other existing branches. Actions authenticates through OIDC, so the workflow does not need a stored Holocron key. Local runs only edit MDX; they do not create a branch or pull request.

     Use `--all` with `--prompt` or `--prompt-file` for scheduled grammar, SEO, link, translation, and style reviews.

  2. **Copy-paste commands on deploy and subscribe errors** — a missing Pro subscription, missing `--project`, org-scoped key, or GitHub OIDC 401 now prints the exact `holocron` command to run, plus the billing URL when a subscription is required.
</Update>

<Update id={"@holocron.so/vite@0.32.0"} label={"Aug 27, 2026"}>
  ## @holocron.so/vite\@0.32.0

  1. **Customize generated OpenAPI pages with `x-holocron`** — override page metadata, insert compatible Markdown and MDX content, or choose the endpoint URL directly on an OpenAPI operation:

     ```yaml
     paths:
       /users:
         post:
           x-holocron:
             metadata:
               title: Create a new user
               sidebarTitle: Create user
               description: Add a user to the current organization.
             content: |
               <Badge color="blue">1 Credit</Badge>

               <Note>
               User email addresses must be unique.
               </Note>
             href: /api-reference/users/create
     ```

     `metadata` supports `title`, `sidebarTitle`, and `description`. `content` renders before the generated endpoint header, description, authorization, parameters, and request fields. `href` replaces the generated slug and bypasses the OpenAPI tab's `base` prefix. It must be a non-root, root-relative internal path without a query string, hash, backslash, double slash, or `.` / `..` segment.

     The same page override subset works through Mintlify's `x-mint` extension. `x-holocron` wins field by field when both extensions define a value. Existing `x-mint` specifications can also place `title`, `sidebarTitle`, and `description` directly inside the extension; nested `metadata` values take priority.

  2. **Fix Ask AI and right-sidebar layout** — the page-level AI widget now behaves like a regular first-section aside. It scrolls and unsticks with that section, while later authored asides stay attached to their own headings. Explicit `<Aside full>` blocks keep their multi-section behavior, including when the first full aside owns the AI widget. Like any per-section aside, the widget can make a short first section taller.

     Tall regular and full asides now scroll within the available viewport instead of clipping or shrinking their children. Ask AI also stays still during client navigation between layouts such as OpenAPI and changelog pages, while the intended open and close morph into the chat drawer remains enabled.

  3. **Style tables in generated OpenAPI and MCP descriptions** — GitHub Flavored Markdown tables now use the same editorial components as documentation pages and AI chat, including styled headers, row separators, cell padding, and horizontal scrolling on narrow screens.

  4. **Make code samples denser** — fenced code now renders at about 12px with the default 14px body size. Copyable API request and response panels also use 10px left padding, giving long examples more usable horizontal space while regular MDX tabs keep their existing padding.
</Update>

<Update id={"@holocron.so/vite@0.31.1"} label={"Aug 26, 2026"}>
  ## @holocron.so/vite\@0.31.1

  1. **Scale the sidebar with `--sidebar-font-size`** — sidebar type, icons, padding, and row spacing now use `em` relative to `--sidebar-font-size` (13px default, 14px at `xl`). Override on `:root` to change globally. The sidebar no longer scrolls when the active row is already in view.

     ```css
     :root {
       --sidebar-font-size: 13px;
     }
     ```

     New spacing variables: `--sidebar-icon-size`, `--sidebar-leading-gap`. `--sidebar-indent` is now derived from these two automatically.

  2. **Improved code fence highlighting** — Prism grammars now register on first highlight instead of at module load, so Cloudflare Worker isolates skip that cost on routes that never highlight code. MDX fences color YAML frontmatter, JSX tags, and ESM `import`/`export`. Markdown fences highlight nested fences (e.g. ` ```ts ` inside ` ```md `). HTML `<style>`/`<script>`, HTTP JSON bodies, and CSS/JS extra tokens stay colored.

  3. **Cache the request-time MDX parse** — repeat page views reuse the parsed tree. Cloudflare Workers with a custom domain keep the tree in the Cache API across isolate restarts.

  4. **AI chat polish** — the Ask AI widget morphs smoothly on open/close (440ms open, 340ms close). Reasoning tokens are hidden; only the final answer is rendered. The input clears immediately after sending. The widget uses opaque backgrounds so page content does not show through.

  5. **OpenAPI endpoint pages** — the first response and nested properties open by default. Long endpoint paths are truncated so the HTTP method badge stays visible.

  6. **Color diagram connectors** — Unicode arrows and ASCII connectors in ` ```diagram ` fences now use the structural connector color.

  7. **Thin Lucide icon strokes from 3 to 2.5** — 12px sidebar and tab icons stay sharp instead of looking filled in.
</Update>

<Update id={"@holocron.so/vite@0.31.0"} label={"Aug 26, 2026"}>
  ## @holocron.so/vite\@0.31.0

  Superseded by 0.31.1. See [https://github.com/remorses/holocron/releases/tag/%40holocron.so/vite%400.31.1](https://github.com/remorses/holocron/releases/tag/%40holocron.so/vite%400.31.1)
</Update>

<Update id={"@holocron.so/vite@0.30.0"} label={"Aug 17, 2026"}>
  ## @holocron.so/vite\@0.30.0

  1. **Highlight fenced code on the server** — token colors are in the first HTML response and stay after in-site navigation. MDX fences no longer wait on a client `useEffect` + Prism load.

     The highlighter loads a docs-focused refractor set, not all 297 Prism grammars. That keeps the RSC worker much smaller. Common product-docs languages still highlight: `ts`, `js`, `python`, `go`, `rust`, `bash`, `json`, `yaml`, `php`, `docker`, `scss`, `dart`, `elixir`, `scala`, `lua`, `nix`, `solidity`, `mermaid`, and the rest of the keep list.

     These languages now render as **plain text**:

     * editors and templates: `vim`, `textile`, `pug`, `haml`, `stylus`, `twig`, `ejs`, `erb`, `rest`
     * functional and academic: `lisp`, `scheme`, `racket`, `haskell`, `ocaml`, `elm`, `purescript`, `reason`, `prolog`, `clojure`, `julia`, `matlab`
     * systems and hardware: `llvm`, `nasm`, `armasm`, `wgsl`, `verilog`, `vhdl`, `wren`, `nim`, `odin`, `v`, `pascal`
     * other long-tail: `applescript`, `arduino`, `awk`, `basic`, `bnf`, `coffeescript`, `dot`, `ebnf`, `erlang`, `fsharp`, `javadoc`, `jsonp`, `perl`, `promql`, `puppet`, `rego`, `rescript`, `tcl`, `uri`, `vbnet`

     Unknown languages already rendered as plain text. That behavior is unchanged.

     The code theme now covers every official Prism standard token ([https://prismjs.com/tokens](https://prismjs.com/tokens)) plus the language-specific aliases used in Holocron docs. YAML keys, Dockerfile instructions, CSS selectors, bash variables, JS property access, and diff insert/delete no longer inherit the default text color.

     The `@holocron.so/vite/prism` export is removed.

  2. **New `tailwindSources` plugin option** — extra directories, files, or globs appended as Tailwind `@source` directives next to the pagesDir source. Use this when MDX content is generated outside the project at deploy time (e.g. multi-tenant shells that swap `holocron-data.js` per site), pointing at the code that emits the classNames so those utilities are compiled into the shell CSS.

     ```ts
     holocron({
         pagesDir: './src',
         tailwindSources: ['../converters/src/**/*.ts'],
     })
     ```

     Paths resolve relative to the vite root. The static prefix of each path is validated at build time so a wrong path fails loudly instead of silently missing classes.

  3. **Allow a URL or root-absolute path in page frontmatter `icon`** — runtime provider pages can now set `icon: https://cdn.example.com/rocket.svg` (or `/icons/rocket.svg`) and the sidebar renders it as an image. Library names, prefixed names, and emoji still work. This lets request-time pages show icons without adding them to the build-time icon atlas.

     ```mdx
     ---
     title: Hello World
     icon: https://cdn.example.com/rocket.svg
     ---
     ```

  4. **Normalize nested sidebar folders so they match page rows** — collapsible nested groups used to render at `--type-nav-group-size` (12px) with a tighter gap under the label, which made folders read as a smaller, separate tier from the pages around them. A nested group row is now visually a page row: same inherited font size, same medium weight, same `gap-1.5` leading slot (the chevron sits where a page's icon sits), and the same vertical rhythm between every row.

     `--sidebar-indent` now defaults to `18px` instead of `12px` so one nesting step equals the width of that leading slot. Nested pages line up exactly under their group's label while the chevron stays in the gutter, giving the sidebar a proper file-tree alignment with or without page icons. Override the token to get a tighter tree.

     Sidebar row highlights are no longer clipped. Hover, active and focus states used to be painted *outside* the row with a `box-shadow` spread, but the sidebar `<nav>` is `overflow-y-auto`, which per spec also clips horizontally, so the left rounded corners were sliced flat against the clip edge. Rows now carry real horizontal padding (`--sidebar-row-padding-x`, cancelled by an equal negative margin so the text column is unchanged) and the highlight is the row's own background, which can never be clipped. The browser focus ring is pulled inside with a negative `outline-offset` for the same reason.

     `--sidebar-link-radius` now defaults to `6px` instead of `0px`, so hover and active states read as a rounded pill. Set it back to `0px` for square rows.

  5. **Give sidebar nav rows more padding inside the hover and active pill** — the highlight used to hug the label with no vertical padding and only `6px` on the sides. Rows now use `--sidebar-row-padding-y` (`4px`) and a slightly wider `--sidebar-row-padding-x` (`8px`). `--sidebar-row-gap` drops from `10px` to `4px` so the space between items stays about the same.

     Override the tokens if you want a tighter or roomier tree:

     ```css
     :root {
       --sidebar-row-padding-x: 8px;
       --sidebar-row-padding-y: 4px;
       --sidebar-row-gap: 4px;
     }
     ```

  6. **Scroll the left sidebar to the current page on load and on client navigation** — deep links into a long nav used to leave the tree at the top, so the active row sat off-screen. The current page now gets `aria-current="page"`. Chrome and Edge use `scroll-initial-target` for the first paint. Other browsers and client-side navigations call `scrollIntoView({ block: 'nearest' })` from a stable ref. Ancestor groups of the current page still open so the row exists in the layout before that scroll runs.

  7. **Replace the temporary AI model warning on free sites with a Holocron promotion** — the callout shows the Holocron wordmark, the product headline, a link to holocron.so, and a note for site owners that a Pro subscription removes it.

  8. **Keep the sidebar AI textarea as a normal input when a past chat exists** — the heading switches to **Open existing chat** with a message-circle icon; focusing the textarea no longer opens the drawer.

  9. **Keep used icon SVGs in the build cache** instead of shipping every Lucide and Font Awesome glyph in the worker.

     The request loader used to import the full Iconify packs (\~2 MB of SVG JSON) so it could look up `lucide:rocket` and similar names. The worker now reads a small atlas of the icons the site actually uses, stored in `dist/holocron-mdx.json` next to the other sync caches. Later builds and dev reloads reuse those bodies and only load Iconify when a new icon name appears.

     Icons in the navbar, sidebar, and MDX still render the same way. No `docs.json` change is required.

  10. **Fix AI chat answers that never arrived** — assistant text is buffered and rendered as one markdown block, so a stream that ended without a `text-end` chunk (provider hiccup, dropped connection) silently discarded the whole answer and left an empty bubble. The buffer is now always flushed when the stream ends.

      Also fixed in the same path:

      * Provider failures are shown instead of swallowed. The AI SDK reports them as `error` chunks rather than throwing, and those chunks were ignored.
      * Error notices are no longer hidden behind the "Temporary AI model" advisory, which used to suppress every later notice in the conversation. Notices now carry a `display` policy: standing advisories show once, per-turn outcomes (rate limits, credit limits, errors) show every time.
      * Reasoning output is kept and rendered as a collapsed "thinking" preview, so a turn whose answer lands in reasoning is still visible.
      * Scratchpad tags some models emit (`<think>`, `<thinking>`, …) are rendered as nothing instead of taking the surrounding answer down with them.
      * A turn that produces nothing renderable now says so instead of showing an empty message, and exhausting the client-tool round limit reports a clear error instead of stopping without an answer.
      * Every chat turn logs a one-line outcome (`[holocron:chat] turn …`) with text size, tool calls and timings, so lost answers are visible in worker logs.

  11. **Fix dev HMR for new OpenAPI and MCP pages** — editing a spec or MCP definition now creates the new page without a dev-server restart. Refractor grammar registration is idempotent, so the RSC remount after a provider sync no longer crashes the module runner.

  12. **Avoid repeating the site name in SEO titles** when a page title already starts with it. For example, a page titled `Holocron - Quickstart` now stays unchanged instead of becoming `Holocron - Quickstart — Holocron`.

  13. **Escape slugs in `generateHolocronData` `import()` paths** with `JSON.stringify`.

      A page slug that contains `"` used to emit invalid JS in `holocron-data.js`:

      ```js
      import("./holocron-page-quotes-"-broken.js")
      ```

      The object key was already stringified. The import specifier is now too, so quotes, backticks, and newlines in slugs no longer crash the worker.

      ```js
      import("./holocron-page-quotes-\"-broken.js")
      ```

  14. **Update the bundled Spiceflow RSC runtime** to `1.26.0-rsc.18`, including the latest Vite RSC plugin fixes.
</Update>

<Update id={"@holocron.so/cli@0.21.1"} label={"Aug 17, 2026"}>
  ## @holocron.so/cli\@0.21.1

  1. **Update the bundled Spiceflow RSC runtime** to `1.26.0-rsc.18`, including the latest Vite RSC plugin fixes.
</Update>

<Update id={"@holocron.so/cli@0.21.0"} label={"Jul 28, 2026"}>
  ## @holocron.so/cli\@0.21.0

  1. **`holocron diagrams fix` now formats GFM tables in place**, not just box-drawing diagrams:

     ```bash
     npx -y "@holocron.so/cli" diagrams fix docs/**/*.mdx
     ```

     Before:

     ```md
     |Name|Age|City|
     |---|---|---|
     |Alice|30|NYC|
     |Bob|2|SF|
     ```

     After:

     ```md
     | Name  | Age | City |
     | ----- | --- | ---- |
     | Alice | 30  | NYC  |
     | Bob   | 2   | SF   |
     ```

     Each table is found via the mdast AST, stringified with the same `mdast-util-gfm` path used by Holocron `.md` / `.mdx` handlers (padded columns, aligned pipes), then spliced back into the original source. The full MDX document is never re-serialized, so prose, JSX, and code fences stay untouched. It also normalizes a blank line above and below each table, and peels trailing prose that GFM would otherwise absorb when a table is missing its closing blank line. `--check` fails on unformatted tables the same way it does for misaligned diagrams.

  2. **`holocron deploy` auto-detects the Vite `base` path** — previously a site built with `base: '/docs'` emitted HTML referencing `/docs/assets/*`, but the deployment metadata had no base path, so the hosting worker looked up assets at root and every CSS/JS request 404ed. The base is now read from the build output and forwarded as the deployment `basePath` (equivalent to passing `--base-path`). An explicit `--base-path` flag still takes precedence.
</Update>

<Update id={"@holocron.so/vite@0.29.0"} label={"Jul 28, 2026"}>
  ## @holocron.so/vite\@0.29.0

  1. **Render OpenAPI `x-codeSamples` as Request example tabs** — SDK snippets from Stainless, Speakeasy, hey-api, or hand-written samples now show up next to the generated cURL block on endpoint pages.

  2. **Tab selection syncs by title across panels** — a new `sync` prop makes `<Tabs>` publish the active tab title so other synced tab groups follow along:

     ```mdx
     <Tabs sync>
       <Tab title="npm">...</Tab>
       <Tab title="pnpm">...</Tab>
     </Tabs>
     ```

     OpenAPI Request example panels enable it by default, so a language choice sticks while browsing between endpoints.

  3. **New `sidebar.animate` config field** — enables expand/collapse and hover transitions on the left navigation tree:

     ```json
     {
       "sidebar": {
         "animate": true
       }
     }
     ```

     The `sidebar` object is extensible for future sidebar settings.

  4. **Sidebar heading list hidden on pages with `<TableOfContentsPanel />`** — the table of contents is already visible in the right aside, so repeating it under the active page entry was redundant. Detection is automatic at build time. A new `sidebarToc` frontmatter field overrides the behavior in either direction:

     ```yaml
     ---
     title: My Page
     # false: always hide sidebar headings for this page
     # true: always show them, even with a TableOfContentsPanel present
     sidebarToc: false
     ---
     ```

     Search results still show matched headings regardless of suppression, so heading hits stay reachable.

  5. **OpenAPI endpoint pages no longer render Path, Header, and Cookie Parameters sections** — path params are already visible in the endpoint path shown at the top of the page, and header/cookie params are internal plumbing better documented in the endpoint description. Only Query Parameters and Request Body keep dedicated sections.

  6. **Fixed images breaking when a Vite `base` path is configured** — images resolved from `public/` (e.g. `/images/inbox.png`) and copied `/_holocron/images/<hash>` paths are now prefixed with the Vite base at render time (`Image`, `LazyVideo`, `Card img`, and frontmatter `og:image`/`twitter:image` meta tags), so a site served under `base: '/docs'` no longer 404s on images. The page cache key now also includes each image's resolution state, so moving an image between the project root and `public/` — or replacing its pixels in place — invalidates the cache instead of serving stale paths until `dist/` is deleted.

  7. **Fixed Cloudflare Workers deploys 404ing every asset when `base` is set** — a site built with `base: '/docs'` emits HTML referencing `/docs/assets/app.js`, but Cloudflare's Asset Worker resolves requests against the uploaded directory tree and deliberately does not strip the base ([cloudflare/workers-sdk#11857](https://github.com/cloudflare/workers-sdk/issues/11857)), so it only had `assets/app.js`. Every script, stylesheet, font, and image failed and the deployed site rendered unstyled. The client build output is now nested under a folder named after the base whenever the Cloudflare plugin is in use. Node deploys and `holocron deploy` are unchanged.

  8. **Fixed hosted deploys breaking when `base` is set in `vite.config.ts`** — the Vite plugin now records the resolved base in `dist/.holocron/holocron-deploy.json` during deploy builds, so `holocron deploy` can detect it and forward it as the deployment `basePath`. Previously the deployment metadata had no base path, the hosting worker looked up assets at root, and every CSS/JS request 404ed.

  9. **Fixed transient 500 ("There is a new version of the pre-bundle") on the first dev server requests** — the SSR optimizer discovered `motion/react` mid-request on the first page render, re-optimized, and invalidated in-flight module graphs. The dep is now pre-included in `optimizeDeps` for the SSR and client environments (along with `github-slugger` and `@radix-ui/react-dropdown-menu`), eliminating the post-startup "optimized dependencies changed. reloading" churn.

  10. **Changelog release dates are formatted in UTC** — a release published at `2026-01-05T00:00:00Z` was labelled with the build machine's local calendar day, so the same GitHub release rendered as `Jan 5, 2026` in Europe and `Jan 4, 2026` in the US. The generated page (and its cache entry) now reads the same everywhere.

  11. **Code blocks in the AI chat panel drop line numbers and right-edge bleed** — the chat column is much narrower than a docs page, so the number gutter ate horizontal space and the bleed pushed code past the panel padding. Docs pages are unchanged.
</Update>

<Update id={"@holocron.so/vite@0.28.0"} label={"Jul 22, 2026"}>
  ## @holocron.so/vite\@0.28.0

  1. **New card, table, and blockquote theming tokens** — cards, tables, and blockquotes are fully customizable from `global.css`:

     ```css
     :root {
       --card-padding: 12px;          /* default 16px */
       --card-border: none;           /* default 1px solid var(--border-subtle) */
       --card-shadow: 0 0 0 1px ...;  /* default none */
       --table-radius: 6px;           /* default 0px */
       --blockquote-font-weight: 500; /* default inherit */
     }
     ```

  2. **Compact search input for all sites** — vertical padding reduced from 6px to 4px (\~30px tall). Focus state shows a subtle ring glow using `--ring` at 25% opacity.

  3. **Sidebar scrollbar no longer overlaps nav items** — the scrollable nav uses padding + negative margin so the OS scrollbar gutter sits outside the content. Hover/active pill spread reduced from 4px to 2px.
</Update>

<Update id={"@holocron.so/vite@0.27.1"} label={"Jul 22, 2026"}>
  ## @holocron.so/vite\@0.27.1

  1. **Fixed broken logo and favicon on sites with a Vite `base` path** — `logo` and `favicon` paths in `docs.json` are now treated as site-root-relative (Mintlify convention) and the Vite `base` (e.g. `/docs/`) is prepended automatically at render time.

     If your `docs.json` manually included the base prefix in logo or favicon paths, remove it:

     ```jsonc
     {
       // before (with base: '/docs/')
       "logo": { "light": "/docs/logos/logo-light.svg" },
       // after — works with any base
       "logo": { "light": "/logos/logo-light.svg" }
     }
     ```
</Update>

<Update id={"@holocron.so/vite@0.27.0"} label={"Jul 22, 2026"}>
  ## @holocron.so/vite\@0.27.0

  1. **New sidebar and code block theming tokens** — restyle the sidebar and code blocks purely with CSS variables, no internal selectors needed:

     ```css
     :root {
       /* Pill behind the deepest active sidebar item — the active TOC heading
          when the current page's TOC is expanded, otherwise the page link */
       --sidebar-active-background: rgba(6, 122, 34, 0.1);

       /* Hover background on sidebar links, group toggles, and TOC headings.
          Defaults to var(--accent) */
       --sidebar-hover-background: rgba(0, 0, 0, 0.06);

       /* Corner radius of the sidebar search input (default var(--radius-xl)) */
       --search-input-radius: 4px;

       /* Box shadow around the code block frame, next to the existing
          --code-block-* tokens (default none) */
       --code-block-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06);
     }
     ```

  2. **Mintlify-compatible `wrap` code fence flag** — soft-wrap long lines instead of horizontal scrolling, ideal for prompt-style text blocks:

     ````mdx
     ```text Example prompt wrap
     Use the Holocron skill. I have a docs site with a broken sidebar link and ...
     ```
     ````

     Wrapped blocks hide line numbers and ignore `highlight` (per-line decorations would misalign across wrapped rows). The bare `lines` flag is also recognized now, equivalent to `lines=true`.

  3. **Concentric chat input corners** — the "Ask AI about this page" widget and chat drawer input now use the concentric radius rule (outer radius minus gap), so the tinted frame keeps uniform thickness around the corners.
</Update>

<Update id={"@holocron.so/vite@0.26.0"} label={"Jul 21, 2026"}>
  ## @holocron.so/vite\@0.26.0

  1. **Redesigned AI chat welcome screen with configurable suggestions** — the empty chat drawer now shows a sparkle illustration, a short pitch of what the assistant can do, and suggestion links styled in the primary color. Suggestions are customizable via `assistant.suggestions` in `docs.json`:

     ```jsonc
     {
       "assistant": {
         "suggestions": [
           "How do I deploy my site?",
           "What MDX components are available?",
           "Search the docs for ..."
         ]
       }
     }
     ```

     The standalone `ChatWidget` accepts the same list via a `suggestions` prop. When omitted, three defaults based on the site name are shown. A suggestion ending with `...` fills the chat input and focuses it instead of submitting, so the user can complete the query.

  2. **Primary-colored chat input** — the drawer input frame and send button now use the primary color instead of gray, with a soft halo on focus.

  3. **Fixed missing chat styles in the embedded widget** — drawer shadow, dropdown animation, and pill styles were silently missing in the embedded holocron path (only the standalone shadow-DOM widget loaded them). Shared chat component styles are now loaded in both paths.
</Update>

<Update id={"@holocron.so/vite@0.25.2"} label={"Jul 16, 2026"}>
  ## @holocron.so/vite\@0.25.2

  **Disable `externalizeShared` by default** — shared dependency externalization is now opt-in. Set `externalizeShared: true` in the Holocron plugin options to re-enable it.

  **Improve AI chat drawer close animation** — the drawer now stays in the DOM while closing (via Motion `AnimatePresence`) so it visibly morphs back into the sidebar "Ask AI" widget or the chat pill with a crossfade, instead of vanishing instantly.
</Update>

<Update id={"@holocron.so/vite@0.25.1"} label={"Jul 15, 2026"}>
  ## @holocron.so/vite\@0.25.1

  1. **Fixed client hydration failure** — disabled `externalizeShared` which caused a circular import map cycle (`Detected cycle while resolving name 'default' in 'react/jsx-runtime'`). All client-side interactivity was broken on deployed sites. Will be re-enabled once spiceflow ships the upstream fix.
</Update>

<Update id={"@holocron.so/vite@0.25.0"} label={"Jul 15, 2026"}>
  ## @holocron.so/vite\@0.25.0

  1. **CSS variables for code blocks, blockquotes, and sidebar navigation** — customize these elements via CSS variables without targeting internal selectors:

     ```css
     :root {
       /* Code blocks */
       --code-block-background: var(--muted);
       --code-block-border: 1px solid var(--border-subtle);
       --code-block-radius: var(--radius-md);
       --code-block-padding-x: 8px;
       --code-block-padding-y: 12px;

       /* Blockquotes */
       --blockquote-border-width: 2px;
       --blockquote-border-color: var(--border-subtle);

       /* Sidebar navigation */
       --sidebar-group-margin-top: 16px;
       --sidebar-link-radius: var(--radius-sm);
       --sidebar-indent: 8px;
     }
     ```

  2. **Search with AI chat** — sidebar search results now show a "Search with AI chat →" action at the bottom. Clicking it opens a new AI chat session with the current search query for deeper answers when keyword results aren't enough.

  3. **Shadow DOM isolation restored for ChatWidget** — the standalone `ChatWidget` renders inside a shadow root again so host page CSS can't leak into chat text. The pill → drawer morph now uses Motion `layoutId` instead of CSS view transitions (Chrome ignores `view-transition-name` inside shadow roots).

  4. **Chat drawer border and layout polish** — the drawer panel and textarea now have visible borders. The Motion layout morph uses `layout="position"` to prevent text stretching during the pill → drawer transition.

  5. **Live config override for dashboard preview** — Holocron sites embedded in the Notaku dashboard can receive live config overrides via query parameters, `postMessage`, or cookies for real-time theme preview without rebuilding.
</Update>

<Update id={"@holocron.so/vite@0.24.0"} label={"Jul 11, 2026"}>
  ## @holocron.so/vite\@0.24.0

  1. **Imageboard tab type** — render a folder of images and videos as a masonry moodboard page. Point a tab at a directory and Holocron builds a responsive CSS-columns grid sorted newest-first by git history. Images get sharp dimensions, pixelated placeholders, and click-to-zoom. Videos get dimensions probed from container headers (no ffmpeg).

     ```json
     {
       "navigation": {
         "tabs": [
           { "tab": "Moodboard", "icon": "images", "imageboard": "./public/moodboard", "columns": 3 }
         ]
       }
     }
     ```

  2. **Persistent AI chat sessions** — conversations survive page refreshes. Every conversation gets a 256-bit session id stored as a cookie (same-origin) or localStorage (cross-origin). Full message history is persisted server-side and restored automatically. Sessions expire after 30 days.

  3. **Session switcher with AI-generated titles** — the chat drawer now has a session select dropdown. Past conversations are listed with AI-generated titles. Picking a session restores it from the server. 'New chat' rotates to a fresh session without deleting the old one.

  4. **WebMCP `document.modelContext` integration** — tools from `defineTool()` are auto-registered on `document.modelContext`, the browser standard for AI agent tool discovery. Third-party tools already on the page are discovered automatically. New exports: `unregisterTool()`, `getRegisteredTools()`, `registerToolOnModelContext()`, `getNativeModelContextTools()`.

  5. **Persistent highlight overlay** — `browser_highlight` now stays visible until the user dismisses it. Lighter dim (15%), optional description card, and the tool returns immediately so the model keeps responding. Pre-action highlights use a quick glow ring with no dim.

  6. **Fin-style textarea pill trigger** — the standalone `ChatWidget` bubble is replaced by a fin.ai-style textarea pill (bottom-right desktop, bottom-center mobile). Expands on focus, morphs into the drawer via view transition. Widget now renders in light DOM (shadow DOM broke view transitions in Chrome). CSS scoped at build time via PostCSS under `.holocron-chat`.

  7. **Human-readable tool call labels** — every tool input now carries a `description` field the model fills with a short summary. Chat UI shows that as the label. `defineTool` auto-injects it. Also fixes stream ordering where assistant text rendered below the tool call.

  8. **Tool approval prompts** — `needsApproval` (boolean or function) shows Approve/Deny before executing. Browser tools auto-require approval for elements with `data-holocron-requires-approval`. Browser tools gained a `description` input for readable approval messages.

  9. **Fixed VideoBackgroundShader WebGL crash** — no longer crashes the site when WebGL is unavailable. Null returns from `createShader`/`createProgram` throw controlled errors, init failures caught gracefully, framebuffer texture probed with fallback chain.

  10. **Fixed `getServerSnapshot should be cached` React warning** in `useChatWidget()`.
</Update>

<Update id={"@holocron.so/cli@0.20.1"} label={"Jul 2, 2026"}>
  ## @holocron.so/cli\@0.20.1

  1. **Diagram fixer handles cross junctions and mixed borders** — `holocron diagrams fix` now correctly detects boxes with cross junctions (`┼`, `╬`, `╋`, `╪`, `╫`) on borders, mixed single/double corners (`╒`, `╓`, `╕`, `╖`, `╘`, `╙`, `╛`, `╜`), and mixed junctions (`╤`, `╥`, `╧`, `╨`, `╞`, `╟`, `╡`, `╢`). Previously these characters broke border scanning and prevented box detection entirely.
  2. **Trailing whitespace stripped from fixed diagrams** — `fixDiagramLines` now trims trailing spaces left by the splice logic when padding adjustments leave no real suffix content.
  3. **Diagram fixer preserves language identifier on fenced code blocks** — opening fence lines like ` ```diagram ` are left untouched during fixing.
</Update>

<Update id={"@holocron.so/vite@0.23.1"} label={"Jul 2, 2026"}>
  ## @holocron.so/vite\@0.23.1

  1. **Fixed excess top padding when Above/Hero is present** — pages with an `above` section (hero content) no longer get 36px of dead space between the header and the hero. The `pt-(--layout-gap)` padding is now conditionally applied only when there is no above content.
  2. **Sitemap.xml now uses navigation tree order** — URLs in `sitemap.xml` now follow the same order as the sidebar, `llms.txt`, and `llms-full.txt` instead of being alphabetically sorted.
</Update>

<Update id={"@holocron.so/vite@0.23.0"} label={"Jun 27, 2026"}>
  ## @holocron.so/vite\@0.23.0

  1. **Custom page mode** — `mode: "custom"` in frontmatter now strips the editorial grid entirely, giving full control over the page content area. Only navbar, tab bar, banner, footer, mobile nav, and AI assistant are rendered. Useful for landing pages, pricing pages, or any page where you want to own the entire layout.

     ```yaml
     ---
     mode: "custom"
     maxWidth: 700
     ---
     ```

     The new `maxWidth` frontmatter field constrains the content container width (in pixels). The navbar still spans full width; only the content area is narrowed.

  2. **HTML comment support in MDX** — `<!-- comments -->` in MDX files are now stripped before parsing so they don't cause JSX parse errors. Supports all edge cases: indented code fences, inline code spans, JSX tag attributes, expressions with `>` operator, tilde fences, and unterminated comments.

  3. **Simplified AI agent redirect** — the `/<slug>.md` redirect for AI coding agents now triggers only on `Accept: text/markdown` instead of User-Agent pattern matching. This eliminates false positives for SEO crawlers (Googlebot, AhrefsBot) that were getting 302 redirected. Raw markdown URLs now include `x-robots-tag: noindex, nofollow` to prevent search engines from indexing duplicate content.
</Update>

<Update id={"@holocron.so/cli@0.20.0"} label={"Jun 27, 2026"}>
  ## @holocron.so/cli\@0.20.0

  1. **Ambiguous Unicode character detection and auto-replacement** — `holocron diagrams fix` now detects characters like `▶`, `◀`, `▲`, `▼`, `★`, `●`, `■` that have Unicode East Asian Width "Ambiguous". These render as 1 cell on macOS/Linux but 2 cells on many Windows monospaced fonts (Consolas, Lucida Console), breaking diagram alignment. The fixer auto-replaces 18 known-ambiguous characters with safe ASCII equivalents (`▶` → `>`, `▼` → `v`, `●` → `*`, etc.) as a first pass before box detection.

     `--check` mode also warns about unreplaceable ambiguous characters that need manual intervention.
</Update>

<Update id={"@holocron.so/outrank@0.1.0"} label={"Jun 26, 2026"}>
  ## @holocron.so/outrank\@0.1.0

  Initial release of the Outrank blog provider for Holocron.

  Fetches articles from the Outrank API at request time (cached with configurable TTL) and renders them as MDX pages. Articles are grouped by their first tag.

  ```ts
  import { outrank } from '@holocron.so/outrank'
  export default outrank({ apiKey: process.env.OUTRANK_API_KEY! })
  ```

  ```json
  { "tab": "Blog", "provider": "./providers/blog.ts", "base": "blog" }
  ```
</Update>

<Update id={"@holocron.so/cli@0.19.0"} label={"Jun 26, 2026"}>
  ## @holocron.so/cli\@0.19.0

  1. **New `holocron diagrams fix` command** — detects and fixes misaligned Unicode box-drawing characters in markdown files. The top border is the source of truth for box width; content lines and bottom borders are adjusted to match.

     Supports light, heavy, double, and rounded character sets. Column-level splice ensures side-by-side and nested boxes don't clobber each other.

     ```bash
     holocron diagrams fix docs/**/*.md
     ```
</Update>

<Update id={"@holocron.so/vite@0.22.0"} label={"Jun 26, 2026"}>
  ## @holocron.so/vite\@0.22.0

  1. **Runtime provider system for custom tab content** — tabs in `docs.json` can now reference a provider file that generates navigation groups and MDX pages at request time. The provider result is cached with configurable TTL and promise coalescing prevents thundering herd on concurrent requests.

     ```json
     {
       "tab": "Blog",
       "provider": "./providers/blog.ts",
       "base": "blog"
     }
     ```

     The provider file default-exports a `CustomTabProvider` object:

     ```ts
     import type { CustomTabProvider } from '@holocron.so/vite'

     const provider: CustomTabProvider = {
       name: 'my-blog',
       static: false,
       ttlMs: 60_000,

       async generate({ tab }) {
         const articles = await fetchArticles()
         return {
           groups: [{ group: 'Posts', pages: articles.map(a => `blog/${a.slug}`) }],
           mdxContent: Object.fromEntries(
             articles.map(a => [`blog/${a.slug}`, `---\ntitle: "${a.title}"\n---\n\n${a.body}`])
           ),
         }
       },
     }

     export default provider
     ```

     Set `static: true` to run the provider at build time instead.

  2. **Strict production builds** — builds now fail on MDX parse errors, unknown component names, broken internal links, broken asset references, and unresolved icon refs. All errors are collected and displayed at once. Set `HOLOCRON_SKIP_BUILD_ERRORS=true` to bypass.

  3. **`/llms-full.txt` route** — every site now serves `/llms-full.txt` alongside `/llms.txt`. Concatenates all documentation pages in docs.json navigation order.

  4. **`iconColor` support** — icons in page frontmatter, tabs, groups, anchors, navbar links, dropdowns, and products now accept an `iconColor` field. Named colors (`green`, `blue`, `red`, `purple`, `orange`, `yellow`, `pink`) adapt to dark mode. Sidebar icons are desaturated 30% and go full saturation on hover/active.

  5. **Site links in sitemap.xml and llms.txt** — external links from navbar, tab bar, anchors, and footer are now surfaced for AI agents and crawlers.

  6. **Redirect destination validation** — redirect destinations in `docs.json` are validated at build time. Typos in destination paths are caught alongside broken internal links.

  7. **Schema validation warnings at startup** — `docs.json` is validated against the schema at build/dev startup.

  8. **Fix heading hash links** — headings with special characters like `+` no longer produce mismatched IDs between DOM and navigation/TOC links.

  9. **Fix footer not reaching viewport bottom** — the section grid no longer creates an extra gap on short pages.

  10. **Fix version selector not resolving hidden pages** — hidden pages now correctly resolve to their owning version.

  11. **Visual refinements** — frame background pattern opacity reduced, lucide stroke-width increased, footer link font size reduced.
</Update>

<Update id={"@holocron.so/cli@0.18.0"} label={"Jun 19, 2026"}>
  ## @holocron.so/cli\@0.18.0

  1. **Custom domain support** — point your own domain (e.g. `docs.mycompany.com`) at your Holocron-deployed docs site. Cloudflare SSL for SaaS handles certificate provisioning automatically. Custom domains require a Pro subscription.

     ```bash
     # Add a custom domain
     holocron domain add --project <projectId> --hostname docs.mycompany.com

     # List domains
     holocron domain list --project <projectId>

     # Check DNS/SSL status
     holocron domain status --project <projectId>

     # Remove a domain
     holocron domain remove --project <projectId> --hostname docs.mycompany.com
     ```

     All custom domains CNAME to `cname.holocron.so`. SSL certificates are provisioned automatically once DNS is configured. The hosting worker activates the mapping only when both hostname and SSL validation are complete, preventing domain front-running.
</Update>

<Update id={"@holocron.so/vite@0.21.0"} label={"Jun 15, 2026"}>
  ## @holocron.so/vite\@0.21.0

  1. **Standalone ChatWidget with shadow DOM isolation** — the AI chat component is now available as a drop-in widget via `@holocron.so/vite/chat`. It renders inside a shadow DOM host so styles don't leak in or out. Supports a `theme` prop (`'light'` | `'dark'` | `'auto'`), a zustand-based `useChatWidget()` hook for programmatic control, and works outside of holocron sites as a standalone component.

  2. **GitHub star count in navbar and footer** — links pointing to GitHub repos now automatically show the star count (e.g. "3.6k stars"). Stars are fetched server-side with a 3-layer cache (in-memory 1h, Cloudflare Cache API 1h, GitHub API fallback) and streamed to the client via RSC so the page renders instantly without blocking.

  3. **Broken asset detection at build time** — local image, video, and audio references in MDX are validated during the build. Missing files produce warnings with page source and line number. Covers markdown images, JSX `<Image>`, `<img>`, `<video>`, `<audio>`, `<source>`, `poster` attributes, and frontmatter `og:image`/`twitter:image` paths. Remote image fetches now have a 5-second timeout to prevent builds from hanging.

  4. **`generateHolocronData` for multi-tenant pipelines** — new export for building holocron data programmatically outside the Vite plugin, useful for multi-tenant hosting platforms that serve many doc sites from a single deployment.

  5. **Deferred virtual tab providers in dev** — OpenAPI, changelog, and MCP providers now run in the background instead of blocking dev server startup. Doc pages are available immediately; provider-generated pages appear a moment later. Build mode is unchanged.

  6. **Resolve relative `og:image` and `twitter:image` URLs to absolute** — social crawlers (Twitter, Discord, Slack, LinkedIn) require absolute URLs. Relative paths like `/images/my-og.png` in frontmatter are now resolved against the request origin automatically. Frontmatter image paths also go through the image pipeline for cache-busted hashing.

  7. **Footer layout improvements** — sites with 2 or fewer link columns now render them inline with the logo on the same row. Sites with 3+ columns center the logo above. Footer group titles are smaller and more refined.

  8. **Tooltips on navbar and footer icons** — icon-only navbar links and footer social icons now show a tooltip on hover with the link label or platform name.

  9. **Auto-unwrap `<p>` from native JSX headings in MDX** — multi-line `<h1>`, `<h2>`, etc. in MDX no longer get wrapped in an `editorial-prose` div. Both single-line and multi-line JSX headings now produce identical clean output.

  10. **Fixed Safari `VideoBackgroundShader` compositing** — premultiplied shader color output before WebGL canvas compositing so low-opacity dots and ASCII glyphs fade correctly in Safari.

  11. **Fixed slug collision in page chunk names** — pages with similar slugs (e.g. `api/users` vs `api--users`) no longer collide. Chunk filenames now include a short hash suffix.

  12. **Fixed sidebar horizontal overflow** — nav items with badges (API method, deprecated, custom tags) now truncate the title instead of pushing the sidebar wider.

  13. **Deterministic named RSC chunks** — virtual modules emit stable chunk names for better caching across builds.
</Update>

<Update id={"@holocron.so/cli@0.17.0"} label={"Jun 15, 2026"}>
  ## @holocron.so/cli\@0.17.0

  1. **New `holocron subscribe` command** — subscribe a project to Holocron Pro directly from the CLI. Opens Stripe Checkout in the browser. Prompts interactively for project and billing interval when flags are omitted:

     ```bash
     # Interactive mode
     holocron subscribe

     # Non-interactive
     holocron subscribe --project <projectId> --interval yearly
     ```

     If the project already has an active subscription, opens the Stripe billing portal instead.

  2. **New `holocron subscription status` command** — check the current subscription state for a project. Works with both session auth and API key auth (`HOLOCRON_KEY`):

     ```bash
     holocron subscription status --project <projectId>
     ```
</Update>

<Update id={"@holocron.so/cli@0.16.0"} label={"Jun 5, 2026"}>
  ## @holocron.so/cli\@0.16.0

  1. **New `--base-path` flag for `holocron deploy`** — deploy your docs at a subpath on your own domain instead of a separate subdomain:

     ```bash
     npx -y @holocron.so/cli deploy --base-path /docs
     ```

     The flag sets Vite's `base` option at build time so all routes and assets are prefixed under the given path. Configure a rewrite or reverse proxy in your framework to forward `/docs/*` requests to the deployed holocron.so URL. Requires a Holocron Pro subscription.

  2. **Fixed device flow login on some servers** — the poll response body was being read twice (once for success check, once for error handling). The second read silently failed, masking expired-token and access-denied errors.

  3. **Allow `holocron login` from AI agent contexts** — removed the `isAgent` guard that blocked login inside agent terminals. The device flow only needs a browser, not interactive stdin.

  4. **Removed `dotenv` dependency** — the CLI gets `HOLOCRON_KEY` from CI env vars or sigillo, not `.env` files, so dotenv was unnecessary.
</Update>

<Update id={"@holocron.so/vite@0.20.0"} label={"Jun 5, 2026"}>
  ## @holocron.so/vite\@0.20.0

  1. **Auto-generate `<meta name="description">` from page body text** — pages without an explicit `description` in YAML frontmatter now get a meta description extracted from the first paragraphs of the MDX content, truncated at \~160 characters on a word boundary. Headings, code blocks, and JSX elements are skipped. Frontmatter `description` always takes precedence.

  2. **Resolve relative MDX links to absolute paths at build time** — relative markdown links like `[guide](./getting-started)` are now resolved to absolute paths during the build so they work correctly regardless of the page's nesting depth. The `.md` and `.mdx` extensions are stripped automatically.

  3. **Subpath hosting support** — when deploying with `--base-path /docs`, the Vite plugin reads `HOLOCRON_BASE_PATH` at build time and injects it as Vite's `base` config, so all routes and assets get the subpath prefix without touching the user's `vite.config.ts`.

  4. **AI chat drawer improvements** — the sidebar AI widget now morphs into the full chat drawer using the View Transitions API with blur cross-fade. Copy/regenerate footer buttons appear on all assistant messages, and the footer visibility gap between submitting a prompt and receiving a response is fixed.

  5. **Fixed `VideoBackgroundShader` crash from culled `texelSize` uniform** — on GPUs that strip inactive uniforms during shader linking, the splat program threw "Unknown uniform: texelSize". The splat program is now excluded from the `texelSize` setup loop since its fragment shader never uses it. Thanks @skeptrunedev for #99!

  6. **Fixed inlined MDX imports with overlapping export/import paths** — when an imported `.mdx` partial contained both an exported string constant and an import with the same relative path, only the actual import source is rewritten now.

  7. **Fixed base path link collision** — replaced spiceflow's built-in `Link` with the holocron wrapper in components that were still using it, preventing broken links when the Vite `base` path collides with a page slug prefix.
</Update>

<Update id={"@holocron.so/vite@0.19.0"} label={"Jun 4, 2026"}>
  ## @holocron.so/vite\@0.19.0

  1. **MCP documentation tabs** — auto-generate documentation pages from MCP (Model Context Protocol) definitions. Point a tab at a local JSON file or a remote Streamable HTTP MCP server:

     ```jsonc
     {
       "tab": "MCP",
       "mcp": "mcp-tools.json",
       "base": "mcp"
     }
     ```

     Each tool becomes a page with its input schema rendered as a parameter field list and a sampled JSON-RPC request example in the sidebar. Resources get their own pages with URI and MIME type. Supports selective mode with `"..."` rest expansion to interleave custom MDX pages with auto-generated ones, same as OpenAPI tabs.

  2. **Changelog `initialContent` field** — prepend custom MDX content above auto-generated release entries in changelog tabs:

     ```jsonc
     {
       "tab": "Changelog",
       "changelog": "https://github.com/owner/repo",
       "initialContent": "changelog/intro"
     }
     ```

     The referenced file goes through the same URL rewriting pipeline as inline `.md` imports, so relative image paths and links resolve correctly.

  3. **OpenAPI multi-status response examples** — the Response panel now shows examples for every response status, not just the first 2xx. When multiple statuses have examples, tab titles are prefixed with the status code for clarity. Closes #98

  4. **OpenAPI spec HMR** — editing a local `.yaml` or `.json` OpenAPI spec now triggers an automatic re-sync so API reference pages update without restarting the dev server.

  5. **VideoBackgroundShader MDX component** — a WebGL-powered dotted video background available directly in MDX as `<VideoBackgroundShader>`. Supports `dotStyle="ascii"` for an ASCII character atlas effect, configurable dot size, color, and fade gradients.

  6. **Same-origin absolute URLs treated as internal links** — absolute URLs pointing to the same site now use client-side routing instead of full page reloads.

  7. **Stripped HTTP method from OpenAPI sidebar labels** — the sidebar already shows a colored method badge, so the text label now omits the method to avoid redundancy.

  8. **Fixed nested list spacing** — nested lists inside `<Li>` elements are properly spaced.

  9. **Fixed duplicate title in code blocks inside Tabs** — code blocks inside `<Tabs>` no longer show the panel title twice.

  10. **Fixed HMR race condition** — concurrent `syncNavigation` calls during rapid file saves are serialized, preventing interleaved state corruption.

  11. **Fixed AI widget aside overlap** — the synthetic AI chat aside only uses `full` mode when the page has no other asides.

  12. **Fixed query string/hash preservation on redirects** — root and markdown-index redirects carry through query parameters and hash fragments.

  13. **Fixed sidebar layout shift** — removed font weight change on active sidebar items.

  14. **Fixed AI chat drawer close behavior** — the chat drawer closes on any link click, not just pathname changes.
</Update>

<Update id={"@holocron.so/vite@0.18.2"} label={"Jun 3, 2026"}>
  ## @holocron.so/vite\@0.18.2

  1. **Fixed false broken link warnings for imported files outside pagesDir** — when a markdown file outside `pagesDir` (e.g., a repo-root `README.md`) is imported into a page and contains relative links back to pages, those links are now correctly resolved to absolute slug paths instead of raw filesystem-relative paths.

     Previously all relative links in such imported files showed as broken even when the target page existed. For example, a `README.md` at the repo root imported via `import Readme from '../../README.md'` that contains `[OpenAPI](./website/src/openapi.md)` now resolves to `/openapi` instead of the unresolvable `../../website/src/openapi`. Hash fragments and query strings are preserved.
</Update>

<Update id={"@holocron.so/vite@0.18.1"} label={"Jun 3, 2026"}>
  ## @holocron.so/vite\@0.18.1

  1. **Restored `motion` as optional dependency** — `motion` was accidentally removed in 0.18.0. Added it back so user projects that depend on it don't break on install.
</Update>

<Update id={"@holocron.so/vite@0.18.0"} label={"Jun 3, 2026"}>
  ## @holocron.so/vite\@0.18.0

  1. **Build summary with actionable tips** — after all individual warnings are logged, the build now prints a final summary:

     ```
     ▲ holocron found 3 invalid internal links across 2 pages.
     ▲ holocron 2 pages with MDX errors. Fix the syntax issues listed above.
     ```

     The broken links summary includes a link to the docs page explaining `knownPaths`.

  2. **`HOLOCRON_TOKEN` accepted as env var alias for `HOLOCRON_KEY`** — the Vite plugin, deploy command, and AI chat auth now check both `HOLOCRON_TOKEN` and `HOLOCRON_KEY` (first defined wins). Useful when your CI already has a `HOLOCRON_TOKEN` secret.

  3. **Fixed OpenAPI renderer CJS crash in dev** — `render-openapi.tsx` was incorrectly marked `'use client'`, pulling `safe-mdx` and its transitive chain into the client bundle. The `format` package (CJS-only) caused:

     ```
     The requested module "format/format.js" does not provide an export named "default"
     ```

     The fix removes the `'use client'` directive so the OpenAPI renderer stays server-side.

  4. **Trimmed Prism syntax highlighting bundle (891 KB -> 471 KB)** — removed \~170 obscure languages, keeping \~130 popular ones. If you need a removed language, open an issue.

  5. **Deduplicated acorn in RSC bundle (180 KB saved)** — a resolve alias now forces all acorn imports to the ESM entry, eliminating the CJS+ESM duplication. RSC bundle: 3,998 KB -> 3,818 KB.

  6. **Removed dead dependencies** — dropped `@fastify/deepmerge`, `image-size`, and `motion` from the package.
</Update>

<Update id={"@holocron.so/cli@0.15.1"} label={"Jun 3, 2026"}>
  ## @holocron.so/cli\@0.15.1

  1. **`HOLOCRON_TOKEN` accepted as env var alias for `HOLOCRON_KEY`** — deploy and all API commands now check both `HOLOCRON_TOKEN` and `HOLOCRON_KEY` (first defined wins). Useful when your CI already has a `HOLOCRON_TOKEN` secret and you don't want to rename it.
</Update>

<Update id={"@holocron.so/vite@0.17.1"} label={"Jun 2, 2026"}>
  ## @holocron.so/vite\@0.17.1

  1. **Fixed OpenAPI double-framed code panels** — when an operation defined multiple named request body or response examples, `RequestExample` and `ResponseExample` wrapped them in a `CodeGroup` inside a `CodeCard`, producing a double frame. Now they render as a single tabbed panel with each example as a named tab.

  2. **Fixed app entry CSS crash without the Vite plugin** — importing `@holocron.so/vite/app` outside the holocron Vite plugin (e.g. inside a `@cloudflare/vitest-pool-workers` test running in workerd) crashed with `Cannot find module './styles/globals.css'`. The import now resolves from the package `src/` directory which is stable from both `src/` and `dist/`.

  3. **Hidden tab scrollbars and press feedback** — the horizontal tab scroll container no longer shows a scrollbar on overflow, and tab buttons no longer flash a press/active highlight.
</Update>

<Update id={"@holocron.so/vite@0.17.0"} label={"Jun 2, 2026"}>
  ## @holocron.so/vite\@0.17.0

  1. **Changelog tab generated from GitHub releases** — add a tab with a `changelog` URL and Holocron fetches the repository's published releases at build time, rendering one page with a Mintlify-compatible `<Update>` entry per release (newest first, drafts skipped):

     ```jsonc
     {
       "tab": "Changelog",
       "changelog": "https://github.com/owner/repo"
     }
     ```

     The generated page uses `mode: center` (left nav hidden) with a right-side notice explaining it's generated from GitHub releases. Release notes are Markdown and are safely escaped so a release body can never break the page. Set a `GITHUB_TOKEN` / `GH_TOKEN` to avoid rate limits; the token is only ever sent to `api.github.com`. Transient GitHub outages render a warning page instead of failing the build.

  2. **Page mode frontmatter (`mode: center`)** — hide the left navigation sidebar per-page, matching Mintlify's `mode` layout control:

     ```mdx
     ---
     title: Landing
     mode: center
     ---
     ```

     Holocron collapses Mintlify's five mode values into two real layouts: `default` / `wide` / `frame` keep the left nav; `center` / `custom` hide it and center the content. All five names are accepted so existing Mintlify frontmatter works unchanged.

  3. **Static page prerendering with `rendering: static`** — opt a page into build-time prerendering. Static pages render to HTML + RSC data at build for faster delivery and cheaper hosting. Use it for pages whose content never depends on the incoming request:

     ```mdx
     ---
     title: Overview
     rendering: static
     ---
     ```

     The default is `ssr`, which renders on every request so pages can react to per-request data like cookies.

  4. **Multiple OpenAPI examples render as switchable tabs** — when an operation defines several named examples (the `examples` map), Holocron renders all of them as a tabbed code group instead of showing only the first. Example names become the tab labels:

     ```yaml
     responses:
       '201':
         content:
           application/json:
             examples:
               Confirmed order:
                 value: { id: 'order-001', status: 'pending' }
               Empty order:
                 value: { id: 'order-002', items: [] }
     ```

  5. **Markdown in OpenAPI descriptions** — `description` fields (Markdown by spec) now render as formatted HTML — headings, lists, inline code, links, emphasis, code blocks — everywhere they appear: endpoint summary, parameters, schema properties, request bodies, and responses. The page `<meta>` description is still flattened to clean plain text. (Closes #96)

  6. **Mix MDX pages with endpoint pages in OpenAPI tabs** — an API Reference tab can now interleave hand-written MDX pages (overviews, authentication guides) with auto-generated endpoint pages.

  7. **`/index` paths resolve to their canonical href** — the `*/index` forms now 308-redirect to the canonical href at runtime (query strings preserved) and count as valid targets for broken-link detection, so links to `/guide/index` no longer 404 or get flagged as broken.

  8. **New `@holocron.so/vite/prism` export** — reuse Holocron's vendored Prism bundle (prismjs core + \~300 grammars in one ESM module) to highlight code outside MDX, without shipping a duplicate bundle:

     ```tsx
     import { Prism } from '@holocron.so/vite/prism'

     const html = Prism.highlight(code, Prism.languages[lang], lang)
     ```

  9. **`bleed` prop on CodeBlock accepts `'both' | 'right' | 'none'`** — control how a code block bleeds into the page margins. `'both'` (or `true`) bleeds both sides, `'right'` only the right margin, `'none'` (or `false`) stays inside the parent.

  10. **Scroll-driven fade mask on the left navigation sidebar** — the sidebar top/bottom edges fade as you scroll.

  11. **Collapsible TOC headings in the left sidebar** — nested headings can now collapse and expand.

  12. **Broken link validation runs during builds** — links pointing to non-existent pages are reported at build time.

  13. **`base` slug prefix accepts a leading slash** — the `base` field on OpenAPI and Changelog tabs is a slug prefix, so a leading slash is now allowed and ignored (`"/docs/api"` behaves like `"docs/api"`); trailing slashes are trimmed. The field was renamed from `openapiBase` to `base` and is now shared across virtual-tab providers.

  14. **`<Above>` hero spans the full grid width** including the side rails.

  15. **Restyled inline code pills** — GitHub-style with baseline alignment; body prose softened so bold text and headings stand out at full foreground color.

  16. **Seamless HMR for `globals.css` edits** — editing Holocron's global stylesheet hot-reloads without a full page refresh.

  17. **Skip auto H1 injection for non-default page modes and JSX-first pages.**

  18. **Fixed per-section asides scoped on heading-first pages.**

  19. **Updated spiceflow to 1.26.0-rsc.3**
</Update>

<Update id={"@holocron.so/cli@0.15.0"} label={"Jun 2, 2026"}>
  ## @holocron.so/cli\@0.15.0

  1. **Clear deploy error when a subscription is required** — when a deploy exceeds the free plan (a preview deploy, or a 2nd production deploy on the free tier), the server returns a `SUBSCRIPTION_REQUIRED` error. The CLI now surfaces the server's actionable message plus the upgrade URL instead of a generic `Failed to create deployment`:

     ```bash
     npx -y @holocron.so/cli deploy
     # A Holocron Pro subscription is required for this deployment.
     # Subscribe to continue: https://holocron.so/...
     ```

  2. **Clearer expired-session message on `login`** — when a saved session token is expired or invalid, the CLI now tells you to run the login command again instead of failing with a confusing error.

  3. **Updated spiceflow to 1.26.0-rsc.3**
</Update>

<Update id={"@holocron.so/vite@0.16.0"} label={"May 27, 2026"}>
  ## @holocron.so/vite\@0.16.0

  1. **New `@holocron.so/vite/mdx` export** — import Holocron MDX components in your own `.tsx` files:

     ```tsx
     import { Card, CardGroup, Callout, Steps, Step } from '@holocron.so/vite/mdx'
     ```

     Useful when building custom components that compose Holocron primitives. In MDX pages, all components remain available globally without imports.

  2. **Image processing preserves user-specified dimensions** — when you set `width` or `height` on `<Image>` or `<img>` in MDX, those values are now preserved instead of being overridden with the natural image size. When only one dimension is provided, the other is computed proportionally from the aspect ratio.

  3. **SVG images skip placeholder generation** — SVG files no longer get a pixelated 16px rasterized WebP placeholder since SVGs are vector and render instantly.

  4. **Fixed images in flex containers** — images inside `<Marquee>`, card grids, and other flex layouts now use a definite pixel width capped at 100% instead of `width: 100%`, which caused circular sizing dependencies in flex items.

  5. **AI logo proxy moved to `/holocron-api/` namespace** — avoids collisions with user API routes.

  6. **AI logo cache improvements** — stale SVG fallback responses are now evicted from the Cache API, and SVG fallbacks are never cached so retries can fetch the real AI-generated image.

  7. **Reduced nav group font size** — sidebar group titles decreased from 13px to 12px for a tighter sidebar.

  8. **Thinner search clear icon** — strokeWidth 1.5 instead of 2.

  9. **Reduced chat input placeholder opacity** — 75% for a subtler appearance.

  10. **Removed vertical margin from Marquee** — lets the component inherit spacing from its parent layout gap.
</Update>

<Update id={"@holocron.so/vite@0.15.0"} label={"May 27, 2026"}>
  ## @holocron.so/vite\@0.15.0

  1. **Keyboard shortcut `d` to toggle dark mode** — press `d` anywhere on the page to switch between light and dark mode. Skips when focus is in an input, textarea, or contenteditable, and ignores modifier combos (Cmd+D, Ctrl+D, etc.)

  2. **Styled `<blockquote>` for plain MDX** — standard markdown `> quoted text` now renders with a left border accent and italic muted text. GitHub-style callouts (`> [!NOTE]`, etc.) still render as Callout components

  3. **New `--type-nav-group-size` CSS variable** — controls font-size of sidebar group titles. Override it to customize sidebar typography:

     ```css
     :root {
       --type-nav-group-size: 14px;
     }
     ```

  4. **Smarter AI chat assistant** — gives shorter, messenger-style answers. Prefers linking to docs pages over re-explaining content

  5. **Fixed ai-logo proxy crash in Dynamic Workers** — `caches.open()` could throw in hosted environments where Cache API is unavailable, causing a 500. Now falls back to direct fetch

  6. **Fixed theme shortcut firing during input** — components that call `preventDefault()` no longer accidentally trigger the dark mode toggle

  7. **Removed paragraph opacity 0.82** — body text no longer renders at reduced opacity

  8. **Fixed subpath externalization in client builds** — `addNoExternal` for `@holocron.so/vite` now runs in all Vite environments (client, ssr, rsc)

  9. **Updated spiceflow to 1.26.0-rsc.0**
</Update>

<Update id={"@holocron.so/cli@0.14.1"} label={"May 27, 2026"}>
  ## @holocron.so/cli\@0.14.1

  1. **Updated spiceflow to 1.26.0-rsc.0**
</Update>

<Update id={"@holocron.so/vite@0.14.3"} label={"May 25, 2026"}>
  ## @holocron.so/vite\@0.14.3

  1. **Fixed spiceflow resolution in strict pnpm workspaces** — spiceflow is a transitive dep of `@holocron.so/vite` and not hoisted in strict pnpm. Virtual modules like `virtual:app-entry` can't resolve bare `spiceflow` imports because they have no filesystem location. A `resolveId` hook now resolves spiceflow from holocron's own source directory.
  2. **Logo text uses heading font** — navbar logo text now uses the configured heading font-family at 22px with weight 560 and tighter letter-spacing.
  3. **Removed preserveSymlinks resolver** — eliminated the custom `@holocron.so/vite/src/*` resolveId hook that was only needed when spiceflow was a workspace dependency.
  4. **Updated spiceflow to 1.25.4-rsc.0**
</Update>

<Update id={"@holocron.so/vite@0.14.2"} label={"May 25, 2026"}>
  ## @holocron.so/vite\@0.14.2

  1. **Logo text uses heading font** — the logo text in the navbar now renders with the configured heading font-family, heavier weight (560), and tighter letter-spacing at 22px, matching the editorial heading style.
  2. **Removed preserveSymlinks resolver** — eliminated custom `resolveId` hooks for `spiceflow` and `@holocron.so/vite/src/*` that were only needed when spiceflow was a workspace dependency. Vite's default resolution now handles everything correctly, fixing transitive dep resolution in strict pnpm workspaces.
  3. **Updated spiceflow to 1.25.4-rsc.0**
</Update>

<Update id={"@holocron.so/vite@0.14.1"} label={"May 25, 2026"}>
  ## @holocron.so/vite\@0.14.1

  1. **DialKit config panel persists open/closed state** — the config panel no longer resets to its default state on page refresh or RSC remount. Open/closed state is saved to localStorage so the panel stays how you left it.
  2. **Reduced main bundle size** — DialKit is now fully lazy-loaded. A stray value import was pulling the entire dialkit package into the eager bundle; switching to `import type` ensures it only loads when the config panel is opened.
</Update>

<Update id={"@holocron.so/cli@0.14.0"} label={"May 25, 2026"}>
  ## @holocron.so/cli\@0.14.0

  1. **Rich `whoami` command with multi-org support** — `holocron whoami` now shows your user info, all organizations with IDs and roles, and projects grouped per org:

     ```bash
     npx -y @holocron.so/cli whoami
     ```

  2. **Multi-org project creation** — `holocron projects create` accepts `--org [orgId]` to target a specific organization. When you belong to multiple orgs and no `--org` is passed, an interactive picker appears.

  3. **Deploy project picker shows org names** — when deploying with multiple projects across orgs, the interactive picker now displays the org name alongside each project.

  Thanks @tanishqkancharla for directing most of the UI changes!
</Update>

<Update id={"@holocron.so/vite@0.14.0"} label={"May 25, 2026"}>
  ## @holocron.so/vite\@0.14.0

  1. **Analytics integrations for 14 providers** — new `integrations` field in docs.json injects client-side analytics scripts. Supports Fathom, Plausible, Pirsch, GA4, GTM, PostHog, Mixpanel, Hotjar, Heap, Segment, Clarity, Amplitude, LogRocket, and Clearbit:

     ```json
     {
       "integrations": {
         "ga4": { "measurementId": "G-XXXXXXXXXX" },
         "plausible": { "domain": "docs.example.com" },
         "fathom": { "siteId": "ABCDEF" }
       }
     }
     ```

  2. **New `<Marquee>` MDX component** — infinite scrolling content strip, available directly in MDX without imports. Supports horizontal and vertical directions, fade edges, configurable speed, and hover deceleration:

     ```mdx
     <Marquee duration={30} fade slowOnHover>
       <img src="/logos/github.svg" />
       <img src="/logos/vercel.svg" />
     </Marquee>
     ```

  3. **`logo.text` config field** — display a site name next to the logo in the navbar:

     ```json
     { "logo": { "light": "/favicon.svg", "text": "My Docs" } }
     ```

  4. **Layout and typography controls in docs.json** — new `layout` (maxWidth, sidebarWidth, columnGap, radius) and `fonts` (fontSize, heading.fontSize) fields for customizing page geometry and font sizes without custom CSS.

  5. **Live config panel on preview deployments** — a DialKit-powered config panel appears in dev mode and on preview deployments, letting you live-tweak colors, layout, fonts, and more.

  6. **Relative-path global anchors with client-side navigation** — anchors pointing to relative paths now use client-side navigation instead of full page reloads.

  7. **Version/dropdown inner tabs visible in the tab bar** — inner tabs inside versions/dropdowns now correctly appear in the header tab bar.

  8. **Search UI improvements** — keyboard-layout-aware shortcut hints, accent border on focus, clear button, and "/" shortcut alongside Cmd+K/Ctrl+K.

  9. **Lazy-loaded Prism.js** — syntax highlighting loaded via dynamic import for faster initial page render.

  10. **Safe-mdx render errors shown in-page during dev** — missing components and invalid JSX now display as a Warning callout directly on the page.

  11. **CSS variable border-radius tokens** — all border-radius values now derive from `--radius`, customizable via `layout.radius`.

  12. **`sidebarTitle` frontmatter** — use a long `title` for SEO while keeping the sidebar label short.

  13. **Fixed ordered list numbering** — lists split by code blocks now continue numbering correctly.

  14. **Fixed theme token cascade** — Holocron tokens now use a low-priority CSS cascade layer so user CSS overrides them properly.

  15. **Fixed sidebar search filtering** — search results update synchronously, fixing flaky states.

  16. **UI polish** — hover background on cards, bottom border on last table row, cleaner active tabs, differentiated sidebar group labels, wider API reference aside panel (460px), hidden line numbers in API reference examples, and more.

  Thanks @tanishqkancharla for directing most of the UI changes!
</Update>

<Update id={"@holocron.so/cli@0.13.0"} label={"May 21, 2026"}>
  ## @holocron.so/cli\@0.13.0

  1. **New `--key` option for `holocron create`** — pass an existing API key to skip the entire cloud setup flow (no device flow login, no project creation, no API key creation). The key is written directly to `.env`:

     ```bash
     npx -y @holocron.so/cli create my-docs --key holo_xxxxxxxxxxxx
     ```

     This enables one-step scaffolding when the key is already known, e.g. from the holocron.so dashboard.
</Update>

<Update id={"@holocron.so/vite@0.13.0"} label={"May 21, 2026"}>
  ## @holocron.so/vite\@0.13.0

  1. **Anchor placement in tabs or sidebar** — anchors now support a `placement` field that controls where they render. `"sidebar"` (the new default) places anchors at the top of the left navigation sidebar with icon and label. `"tabs"` places them in the header tab bar, preserving the previous behavior. You can mix both placements in the same config:

     ```json
     {
       "navbar": {
         "links": [
           { "label": "GitHub", "url": "https://github.com/...", "placement": "sidebar" },
           { "label": "Changelog", "url": "/changelog", "placement": "tabs" }
         ]
       }
     }
     ```

  2. **Copy-to-clipboard button on code blocks** — every fenced code block now shows a copy button on hover in the top-right corner. The button fades in, transitions to a checkmark icon on success, and gracefully handles clipboard write failures in insecure contexts.

  3. **`.md`/`.mdx` extensions stripped from internal links** — links like `[guide](/getting-started.md)` are now automatically rewritten to `/getting-started` at build time. Previously these links were excluded from broken-link validation and would serve raw markdown or 404. Reference-style links and JSX `href` attributes are also handled.

  4. **Dark mode persists across reloads** — a blocking `<script>` now reads the `holocron-theme` cookie before first paint, preventing the theme from flashing or resetting during RSC streaming.

  5. **Stale build artifacts cleaned before each build** — `vite build` now removes old `client/`, `rsc/`, `ssr/` directories from `dist/` before building, preventing stale artifacts from leaking into fresh builds. Cache files are preserved for incremental builds.
</Update>

<Update id={"@holocron.so/vite@0.12.0"} label={"May 21, 2026"}>
  ## @holocron.so/vite\@0.12.0

  1. **Code block meta props: bleed, lines, title, highlight** — fenced code blocks now support meta string options parsed at build time:

     ````md
     ```ts title="vite.config.ts" highlight="3-5" lines=false bleed=true
     import \{ defineConfig \} from 'vite'
     import holocron from '@holocron.so/vite'

     export default defineConfig(\{
       plugins: [holocron()],
     \})
     ```
     ````

     * `title="..."` or bare words → filename/label header above the code block
     * `highlight="1-3,7"` → dims non-highlighted lines with a background overlay
     * `lines=false` → hides line numbers (on by default)
     * `bleed=true` → extends the code block into page margins

  2. **Improved inline code in headings** — inline code inside headings now inherits the heading font size and weight instead of shrinking to `0.875em`. Background pill is hidden, color uses full-contrast `var(--foreground)` to match heading text. H2 headings also get a decorative divider line (previously h1 only).

  3. **Theme-adaptive diagram labels** — ASCII diagram labels in code blocks now use `var(--primary)` instead of hardcoded green values, so they automatically match the site color theme.

  4. **Tighter line-height for diagram code blocks** — code blocks with diagram languages (`diagram`, `ascii`, `box`) use `line-height: 1.3` instead of `1.6`, improving vertical alignment of box-drawing characters and connected lines.

  5. **Inline code color fix** — `.inline-code` now uses `var(--foreground)` in both light and dark mode instead of hardcoded `rgba()` values, so it adapts to custom themes.
</Update>

<Update id={"@holocron.so/vite@0.11.0"} label={"May 20, 2026"}>
  ## @holocron.so/vite\@0.11.0

  1. **Inline `.md`/`.mdx` imports at remark level** — imported markdown files are now spliced directly into the page's mdast tree before any remark plugins run. Headings from imported files appear in the TOC automatically, images go through the normal build-time processor, and all remark plugins (callouts, code groups, mermaid, etc.) apply to the inlined content. Recursive imports are supported with cycle detection. Each imported file is parsed exactly once and the mdast is reused for import extraction, image dep collection, and pre-building spliced nodes. Pages without `.md` imports skip the full AST parse entirely via a regex fast path.

  2. **New `knownPaths` config field** — suppress false broken-link warnings when mounting docs alongside other routes (API endpoints, dashboards, external apps). Supports exact paths and prefix wildcards:

     ```json
     {
       "knownPaths": ["/api/*", "/dashboard", "/blog/*"]
     }
     ```

  3. **Imported `.md`/`.mdx` files included in AI chat context** — imported markdown snippets and shared fragments are now sent to the AI chat agent during local dev, matching production behavior where they were already included via `docs.zip`.

  4. **Content column capped at 720px** — the derived `--grid-content-width` is now wrapped in `min(720px, ...)` so it never grows beyond 720px regardless of grid geometry.

  5. **Fixed heading anchor scroll offset** — `scroll-margin-top` now uses `--sticky-top` instead of `--header-height`, adding breathing room below the navbar when navigating to `#id` anchors.

  6. **Fixed AI chat loading dots alignment** — loading indicator dots are now aligned to top-start instead of vertically centered.
</Update>

<Update id={"@holocron.so/vite@0.10.2"} label={"May 19, 2026"}>
  ## @holocron.so/vite\@0.10.2

  1. **Fixed custom entry CSS under Cloudflare dev** — custom-entry apps now correctly load Holocron's global stylesheet when running under `wrangler dev`. The Spiceflow RSC entry now uses the real custom entry file instead of routing through a virtual module, so vite-rsc can walk the import graph and collect all CSS dependencies.

  2. **Removed manual safe-mdx aliases** — safe-mdx 1.11.1 ships a package-level `react-server` export map fallback, so Holocron no longer needs to carry private path aliases for `safe-mdx`, `safe-mdx/parse`, and `safe-mdx/client`.

  3. **Fixed broken 0.10.1 publish** — 0.10.1 was published with npm instead of pnpm, leaving `workspace:^` references unresolved in the published package.json.
</Update>

<Update id={"@holocron.so/vite@0.10.1"} label={"May 19, 2026"}>
  ## @holocron.so/vite\@0.10.1

  1. **Fixed custom entry CSS under Cloudflare dev** — custom-entry apps now correctly load Holocron's global stylesheet when running under `wrangler dev`. The Spiceflow RSC entry now uses the real custom entry file instead of routing through a virtual module, so vite-rsc can walk the import graph and collect all CSS dependencies.

  2. **Removed manual safe-mdx aliases** — safe-mdx 1.11.1 ships a package-level `react-server` export map fallback, so Holocron no longer needs to carry private path aliases for `safe-mdx`, `safe-mdx/parse`, and `safe-mdx/client`.
</Update>

<Update id={"@holocron.so/vite@0.10.0"} label={"May 19, 2026"}>
  ## @holocron.so/vite\@0.10.0

  1. **Build-time processing for imported `.md`/`.mdx` files** — imported markdown files (e.g. `import Guide from "./snippets/guide.md"`) now go through the same build pipeline as regular pages: all remark plugins (GitHub callouts, code groups, etc.), image resolution (dimensions, placeholders, copy to public), and normalization. Previously these were loaded as raw strings and parsed at render time without any processing.

  2. **Imported MDX headings appear in sidebar TOC** — headings from imported `.md`/`.mdx` files now show up in the left sidebar table of contents in correct document order. Previously imported components were opaque JSX nodes, so their headings were invisible to the TOC.

  3. **Broken internal link warnings during sync** — Holocron now walks the mdast tree during sync and resolves every internal link against the page index and redirect sources. Links pointing to non-existent pages log a warning with source location. Handles absolute (`/foo`), relative (`./foo`, `../bar`), hash fragments, and query strings.

  4. **Serve raw markdown at `.mdx` URLs** — pages were already served as raw markdown at `/<slug>.md` for AI agents. Now `.mdx` URLs work identically, returning the same content with `text/markdown` content-type.

  5. **Imported files included in `/docs.zip`** — the `/docs.zip` endpoint now includes imported markdown files (snippets, shared fragments, files outside pagesDir) alongside navigation pages.

  6. **Global CSS loads for custom entries** — custom-entry apps that mount Holocron through a user-owned Spiceflow entry now correctly load Holocron's global stylesheet.

  7. **Spiceflow moved to regular dependencies** — users no longer need to install spiceflow separately. It ships as a regular dependency, so `pnpm install` resolves it automatically.

  8. **Fixed copy-as-markdown button on index pages** — the button was fetching `/.md` (404) on the root page instead of `/index.md`.

  9. **Fixed link hydration mismatch** — `isExternalHref` used `new URL()` origin comparison that produced different results on server vs client for relative paths. Replaced with a consistent regex.

  10. **Fixed empty sidebar group labels** — unnamed sidebar groups no longer render an empty `<div>` wrapper.

  11. **Fixed config HMR** — editing `docs.json` colors and styles now hot-reloads correctly without stale CSS artifacts.
</Update>

<Update id={"@holocron.so/cli@0.12.2"} label={"May 19, 2026"}>
  ## @holocron.so/cli\@0.12.2

  1. **Fixed `$schema` URL in scaffolded projects** — `holocron create` now writes `"$schema": "https://holocron.so/docs.json"` instead of the old unpkg URL that depended on npm publish timing and internal file paths.
</Update>

<Update id={"@holocron.so/vite@0.9.0"} label={"May 18, 2026"}>
  ## @holocron.so/vite\@0.9.0

  1. **New prev/next page navigation in the right sidebar** — every page now shows chevron arrows linking to the previous and next pages in navigation order, plus a "Copy as Markdown" button that copies the current page content to clipboard. Tooltips on the arrows show the target page title via portal-based rendering to avoid clipping.

  2. **Frontmatter JSON Schema** — a new `frontmatter-schema.json` is generated alongside the config schema, describing all supported MDX frontmatter fields (title, description, icon, SEO meta, hidden, etc.). Add `$schema: "https://holocron.so/frontmatter.json"` to your MDX frontmatter for IDE autocomplete and validation.

  3. **Icon name autocomplete in `docs.json`** — the config JSON schema now references external enum schemas for lucide and Font Awesome icon names. IDEs that support `$ref` resolution fetch icon name lists on demand from holocron.so, giving you autocomplete for all 4,000+ supported icon names.

  4. **Shared `cn()` utility (clsx + tailwind-merge)** — all components now use a shared `cn()` following the shadcn convention. This fixes a bug where passing `className` to `<Logo>` would completely replace the base sizing classes instead of merging with them. All components with className props now merge correctly via `tailwind-merge`.

  5. **`text` prop on `<Logo />`** — pass `<Logo text="My Docs" />` to render an AI-generated logo using that text, bypassing the site config logo entirely.

  6. **Fixed Tailwind HMR for MDX page edits** — editing MDX files or imported components no longer triggers a full page reload. New Tailwind utility classes are now compiled and injected in-place during HMR, preserving client state. Previously, Tailwind treated MDX files as external template changes and forced a reload.

  7. **Upgraded Spiceflow to 1.25.3-rsc.0** — aligns all workspace packages on the same RSC build, avoiding duplicate framework versions.

  8. **Search bar focus styling** — replaced the thick box-shadow focus ring with a subtle border-color change to `muted-foreground` for a cleaner active state.

  9. **AI chat polish** — reduced the ShowMore collapsed height from 80px to 40px for tighter tool output previews.
</Update>

<Update id={"@holocron.so/cli@0.12.1"} label={"May 18, 2026"}>
  ## @holocron.so/cli\@0.12.1

  1. **Upgraded Spiceflow to 1.25.3-rsc.0** — aligns with the latest RSC build used by `@holocron.so/vite`, avoiding duplicate framework versions at runtime.
</Update>

<Update id={"@holocron.so/vite@0.8.0"} label={"May 14, 2026"}>
  ## @holocron.so/vite\@0.8.0

  1. **New `<Logo />` MDX component** — render the configured site logo directly inside docs content. It uses the same resolved light, dark, and generated logo variants as the navbar and footer.

     ```mdx
     <Logo />
     ```

  2. **Tailwind scans your docs tree** — Holocron now adds your configured `pagesDir` as a Tailwind source, so utility classes used in MDX content and imported docs components are included in the generated CSS.

  3. **Better MDX error pages and build failures** — MDX parse failures stay attached to their page route in dev, rendering a focused error page instead of turning into a 404. Production builds fail with the formatted code-frame message so broken docs do not deploy silently.

  4. **MDX component validation during sync** — Holocron validates rendered MDX against the supported component map while syncing navigation. Unknown components and invalid imported MDX now surface earlier with the page source that caused the failure.

  5. **Self-hosted JetBrains Mono** — code now uses `@fontsource-variable/jetbrains-mono`, avoiding a third-party font request for the default monospace font.

  6. **JSONC syntax highlighting** — fenced `jsonc` code blocks now reuse Prism's JSON grammar instead of rendering without highlighting.

  7. **OpenAPI success responses open by default** — generated endpoint pages now expand successful response examples first, making the useful response shape visible immediately.

  8. **Navigation and layout polish** — browser scroll restoration works across docs navigation, unnamed sidebar groups no longer break search, responsive images keep their intended sizing, Frame captions align better, and the page AI widget stays hidden on mobile.
</Update>

<Update id={"@holocron.so/cli@0.12.0"} label={"May 14, 2026"}>
  ## @holocron.so/cli\@0.12.0

  1. **Scaffolded projects now use `docs.jsonc`** — `holocron create` generates the starter config as JSONC, so new projects can keep comments and trailing commas in the same config file Holocron reads by default.

     ```bash
     npx -y @holocron.so/cli create my-docs
     ```

     The create command now parses the template as JSONC before writing the project name and schema URL, so custom starter templates can use JSONC syntax safely.
</Update>

<Update id={"@holocron.so/vite@0.7.1"} label={"May 13, 2026"}>
  ## @holocron.so/vite\@0.7.1

  1. **Fixed internal links opening in new tabs** — relative links, hash links, and same-origin links in docs content now navigate in-place using client-side navigation instead of opening a new browser tab. External links still open in a new tab as expected.
  2. **Table edges align flush with page content** — removed left padding from the first table column and right padding from the last column so table data aligns with surrounding editorial content.
  3. **Improved AI chat scroll behavior** — after submitting a message, the chat drawer now scrolls your message to the top of the viewport instead of jumping to the bottom. The loading state and assistant response area get enough height to keep the scroll position stable during streaming.
  4. **Fixed chat message overflow** — resolved an issue where `overflow-x-hidden` on chat text containers silently triggered vertical scrollbars due to CSS spec behavior. Content now grows naturally without nested scroll regions.
</Update>

<Update id={"@holocron.so/vite@0.7.0"} label={"May 13, 2026"}>
  ## @holocron.so/vite\@0.7.0

  1. **Auto-derive dark mode `--primary` when `colors.light` is not set** — if you only configure `colors.primary` without an explicit `colors.light`, the dark mode accent was identical to light mode, making links unreadable on dark backgrounds. Now auto-generates a lighter variant via `color-mix(in oklch, <primary> 40%, white)`, roughly matching Tailwind's 200-scale lightness. If you explicitly set `colors.light`, your value is still used as-is.

  2. **Fixed Cloudflare Workers deploy crash ("No such module ssr/isbot")** — the `@cloudflare/vite-plugin` was loaded via async `import()`, leaving an unresolved Promise in the plugins array. Spiceflow couldn't detect it, so `noExternal: true` was never set for SSR/RSC environments. Bare npm imports like `isbot` and `history` stayed external, crashing Dynamic Workers at runtime. The plugin is now imported synchronously and placed before spiceflow in the plugin array.

  3. **Fixed image height override in content area** — a blanket `.slot-main img { height: auto !important }` rule was overriding the explicit `height: 100%` set by the Image component for pixelated placeholder overlays. The global rule has been removed; `height: auto` and `max-width: 100%` are now applied only on specific image paths that need them.

  4. **Tighter TOC panel spacing** — reduced right-sidebar table of contents item vertical padding from `py-1.5` to `py-1`.

  5. **Excluded spiceflow from RSC/SSR optimizeDeps** — prevents Vite from pre-bundling spiceflow in RSC and SSR environments so it stays in the transform pipeline as-is.

  6. **Deploy output writes to `dist/.holocron`** — when `HOLOCRON_DEPLOY=1` is set, the Vite plugin now sets `build.outDir` to `dist/.holocron` instead of `dist/`, keeping deploy artifacts separate from normal platform-specific builds.
</Update>

<Update id={"@holocron.so/cli@0.11.1"} label={"May 13, 2026"}>
  ## @holocron.so/cli\@0.11.1

  1. **Deploy auth check runs before build** — credentials are validated upfront so missing auth fails immediately instead of after a full Vite build
  2. **Deploy output separated from normal build** — `holocron deploy` now writes to `dist/.holocron` instead of `dist/`, keeping deploy artifacts isolated from platform-specific Vite builds (Cloudflare vs Node.js)
  3. **Removed `--skip-build` flag** — builds always run during deploy. The separate output dir makes the flag unnecessary
</Update>

<Update id={"@holocron.so/cli@0.11.0"} label={"May 12, 2026"}>
  ## @holocron.so/cli\@0.11.0

  1. **Keyless deploys from GitHub Actions via OIDC** — `holocron deploy` now supports GitHub Actions OIDC authentication natively. No `HOLOCRON_KEY` secret needed; just set `permissions: id-token: write` in your workflow:

     ```yaml
     permissions:
       id-token: write
       contents: read
     steps:
       - uses: actions/checkout@v4
       - run: npx holocron deploy
     ```

     The CLI mints a fresh OIDC token for each deploy step (create, upload, finalize) and the server derives project, branch, and preview state from the verified JWT claims. API key and session auth continue to work as before.

  2. **Scaffold no longer lists `spiceflow` as a direct dependency** — `holocron create` generates a leaner `package.json`. Spiceflow is a transitive dependency of `@holocron.so/vite` so users don't need to install it separately.

  3. **Improved deploy error messages** — auth failure now suggests all three auth methods (env var, `holocron login`, or GitHub Actions OIDC) instead of only the first two.
</Update>

<Update id={"@holocron.so/vite@0.6.1"} label={"May 12, 2026"}>
  ## @holocron.so/vite\@0.6.1

  1. **`@cloudflare/vite-plugin` is now a direct dependency** — users deploying to Cloudflare Workers no longer need to install it separately. It ships as a transitive dep of `@holocron.so/vite`.

  2. **Fixed OpenAPI spec resolution when `pagesDir` is set** — specs inside a custom `pagesDir` (e.g. `pagesDir: "./src"` with `api.yaml` in `src/`) now resolve correctly. Previously only the project root was probed, causing "OpenAPI spec not found" errors. The error message now lists all probed locations when neither has the file.

  3. **Removed build-time OIDC registration from the Vite plugin** — the OIDC token minting and `.env` write path has been moved to the CLI deploy command. The Vite plugin no longer writes `HOLOCRON_KEY` or `HOLOCRON_BRANCH` to `.env` during build. Deploy authentication is now fully handled by `holocron deploy`.

  4. **Bumped spiceflow peer dep to `>=1.25.1-rsc.0`** — fixes deploy failures where the SSR entry wasn't nested inside the RSC output directory, causing "deployment must include worker/ssr/index.js" errors.
</Update>

<Update id={"@holocron.so/vite@0.6.0"} label={"May 12, 2026"}>
  ## @holocron.so/vite\@0.6.0

  1. **Self-hosted Inter font** — the default Inter font is now bundled via `@fontsource-variable/inter` instead of loading from third-party CDNs (`rsms.me`, Google Fonts). No external font requests on default config. Google Fonts preconnect tags only appear when you explicitly configure a custom Google font.

  2. **OIDC keyless deploys from GitHub Actions** — when `permissions: id-token: write` is set, the Vite plugin automatically mints a GitHub OIDC token and registers the deployment without any secret configuration:

     ```yaml
     # No HOLOCRON_KEY secret needed
     permissions:
       id-token: write
     steps:
       - run: npx holocron deploy
     ```

  3. **Prism excluded from SSR** — syntax highlighting now runs client-only via a `#prism` conditional import. SSR/RSC get a noop stub, then the client adds highlighting during hydration. Reduces SSR bundle by \~500KB and avoids the CJS global crash in Dynamic Workers.

  4. **Stable dependency code splitting** — framework and vendor code is grouped into a single `holocron-stable` chunk in the RSC build. The entry chunk shrinks to \~20KB of virtual modules, while the stable chunk stays content-addressable across deploys for maximum KV dedup.

  5. **`listen()` guard moved to renderChunk** — the auto-start `listen()` call is now appended to the final RSC entry chunk after bundling, keeping `import.meta.url` correct even when code splitting moves framework code into dependency chunks.

  6. **Dynamic Workers `createRequire` fix** — `createRequire(import.meta.url)` calls in bundled CJS helpers are replaced at build time when `HOLOCRON_DEPLOY=1`, preventing module evaluation crashes in Dynamic Workers.

  7. **Auto-inject Cloudflare plugin** — when `HOLOCRON_DEPLOY=1` is set (by `holocron deploy`), `@cloudflare/vite-plugin` is auto-injected. Users don't need it in their `vite.config.ts`.

  8. **Headings with inline code** — headings like `` ### `config` `` now appear correctly in the sidebar and table of contents instead of showing as empty entries.

  9. **Empty headings filtered** — headings with no text content are dropped from the sidebar TOC and right-side table of contents instead of rendering as blank items.

  10. **User entry exports preserved** — custom spiceflow entries now re-export all named exports alongside `app` and `default`.

  11. **`yaml` browser entry alias** — the `yaml` package is aliased to its browser entry at build time, fixing resolution issues in the browser bundle.

  12. **`@cloudflare/vite-plugin` optional peer dependency** — added as optional so `pnpm install` doesn't warn when deploying to non-Cloudflare targets.
</Update>

<Update id={"@holocron.so/cli@0.10.0"} label={"May 12, 2026"}>
  ## @holocron.so/cli\@0.10.0

  1. **New `holocron deploy` command** — build and deploy your docs site to holocron.so with a single command. Content-addressable uploads skip unchanged files across deploys:

     ```bash
     holocron deploy
     ```

     * Auto-detects branch from git, GitHub Actions, or `--branch` flag
     * Zip-batched parallel uploads with progress reporting
     * SHA-256 content hashing; only new/changed files are uploaded
     * Auto-sets `holocron_url` and `holocron_deployment_id` as GitHub Actions step outputs
     * Reads project name from `docs.json` and syncs it server-side
     * Supports `--skip-build` to deploy an existing `dist/`
     * Auth via `HOLOCRON_KEY` env var or `holocron login` session

  2. **Multi-environment auth** — CLI now stores session tokens keyed by server URL, so you can be logged into production and preview simultaneously:

     ```bash
     holocron login                              # logs into holocron.so
     holocron --api-url https://preview.holocron.so login  # separate session
     holocron whoami                              # shows current server's user
     ```

  3. **Global `--api-url` flag** — all commands now respect a top-level `--api-url` option instead of per-command `-u`/`--url` flags.

  4. **Improved `create` command UX** — reuses existing login session instead of re-authenticating, appends `-docs` to the generated folder name, and skips the "start dev server?" prompt when dependencies weren't installed.

  5. **Colored CLI output** — all commands use a centralized logger with color-coded status icons for better readability.

  6. **Non-TTY safety** — `holocron login` fails fast with a clear message in non-interactive environments instead of hanging on stdin.
</Update>

<Update id={"@holocron.so/vite@0.5.0"} label={"May 11, 2026"}>
  ## @holocron.so/vite\@0.5.0

  1. **Decorative grid lines** — configurable vertical lines with dot ornaments at intersections. Set `decorativeLines` in your config to `"none"`, `"lines"`, `"dashed"`, or `"lines-with-dots"` (default):

     ```json
     { "decorativeLines": "dashed" }
     ```

  2. **Per-page CDN caching via frontmatter** — set `cache-control` in page frontmatter to control HTTP caching headers per page:

     ```yaml
     ---
     title: My Page
     cache-control: public, max-age=3600
     ---
     ```

  3. **`?raw` imports in MDX modules** — MDX files can now import raw text content from other files using Vite's `?raw` query suffix.

  4. **`docs.jsonc` config discovery** — Holocron discovers config files in Mintlify-first order: `docs.json`, `docs.jsonc`, then `holocron.jsonc`. JSONC comments and trailing commas work without renaming your Mintlify config.

  5. **`holocron` CLI bundled with vite package** — installing `@holocron.so/vite` now also provides the `holocron` CLI command. No separate `@holocron.so/cli` install needed.

  6. **Deploy with just `HOLOCRON_KEY`** — deployment registration now only needs `HOLOCRON_KEY` (removed `HOLOCRON_PROJECT`). The project is resolved from the key server-side.

  7. **Generated entry guards `listen()` with `import.meta.main`** — the built `dist/rsc/index.js` can now be imported by another framework (e.g. Next.js catch-all route) without starting a second server.

  8. **OG images and logos served from holocron.so** — OG image rendering and logo generation moved to a dedicated Cloudflare Worker, dropping \~5 MiB from the vite plugin bundle.

  9. **Sidebar nav animations disabled by default** — sidebar expand/collapse transitions are off by default, gated behind a `.sidebar-animate` CSS class.

  10. **Config types and schema exported from index** — `@holocron.so/vite` now exports config types and the JSON schema for programmatic config validation.

  11. **Darker dark mode** — dark mode background darkened from `0.21` to `0.16` oklch lightness for better contrast.

  12. **Fixed TOC heading highlight** — same-hash re-click and scrollbar drag edge cases now correctly update the active heading.

  13. **Fixed sidebar heading click** — clicking a heading in the sidebar now highlights correctly after scroll.

  14. **Fixed page overflow** — decorative grid dots no longer extend below the content container.

  15. **Fixed title injection** — pages that already start with any heading level are left untouched.

  16. **Tab link and scrollbar polish** — removed lowercase transform, fixed indicator height, thinner scrollbar thumbs, softer light mode borders.
</Update>

<Update id={"@holocron.so/cli@0.9.0"} label={"May 11, 2026"}>
  ## @holocron.so/cli\@0.9.0

  1. **New `holocron create` command** — scaffold a new docs project from a starter template with interactive setup. Optionally connects to holocron.so for AI chat and analytics:

     ```bash
     holocron create my-docs --name "My Docs"
     ```

     Non-interactive mode supported for CI/agent use. The scaffold includes `docs.json`, MDX pages, `vite.config.ts`, and `.env` with your API key.

  2. **New `projects list` and `projects create` commands** — manage projects for your org:

     ```bash
     holocron projects create --name "My Docs"
     holocron projects list
     ```

  3. **API keys are now project-scoped** — each key is tied to a project. The key alone identifies which project a deployment belongs to, so `HOLOCRON_PROJECT` is no longer needed. Just set `HOLOCRON_KEY`:

     ```bash
     holocron keys create --name production --project <projectId>
     ```

  4. **Renamed `HOLOCRON_API_KEY` to `HOLOCRON_KEY`** — shorter env var name. Update your `.env` and CI secrets.

  5. **Simplified API routes** — the CLI no longer manages org IDs client-side. Org resolution and auto-creation happen server-side.

  6. **`docs.jsonc` config support** — the scaffold now outputs `docs.json` with a `$schema` URL pointing to the published npm package for IDE autocomplete.
</Update>

<Update id={"@holocron.so/vite@0.4.0"} label={"May 2, 2026"}>
  ## @holocron.so/vite\@0.4.0

  1. **Agent discovery endpoints for every docs site**: Holocron now serves the well-known agent-skills discovery files automatically so coding agents can discover and install a docs-specific skill:

     ```txt
     /.well-known/agent-skills/index.json
     /.well-known/agent-skills/{name}/SKILL.md
     /.well-known/skills/index.json
     /.well-known/skills/{name}/SKILL.md
     ```

     The generated skill points agents at `/sitemap.xml`, raw `.md` page URLs, and `/docs.zip`. Base-path deployments use relative URLs, and AI-user-agent redirects skip the well-known routes so JSON discovery stays machine-readable.

  2. **Added `/llms.txt`**: every docs site now exposes a standard agent entry point that links to `/docs.zip` first, then individual raw markdown pages:

     ```txt
     https://docs.example.com/llms.txt
     ```

  3. **Imported MDX and Markdown snippets**: MDX pages can import local `.mdx` and `.md` snippets, including files outside the docs root, and Holocron resolves them through the same safe-mdx rendering pipeline as normal pages:

     ```mdx
     import Intro from './snippets/intro.mdx'
     import Readme from '../../README.md'

     <Intro />
     <Readme />
     ```

  4. **Added Mintlify-compatible `<Visibility>`**: docs can render content only for humans or only for agent-facing markdown output:

     ```mdx
     <Visibility for="humans">
     This appears on the website.
     </Visibility>

     <Visibility for="agents">
     This appears in `.md` routes and docs.zip.
     </Visibility>
     ```

  5. **More Mintlify-compatible MDX components**: callouts, badges, cards, expandables, frames, tooltips, trees, accordions, and tabs accept more Mintlify props without requiring docs rewrites.

  6. **HTML `<details>` support**: copied docs that use native HTML details/summary blocks are normalized into Holocron's existing `Expandable` component.

  7. **GitHub-style callout quotes**: Markdown alerts like `> [!NOTE]`, `> [!TIP]`, and `> [!WARNING]` now render as Holocron callouts.

  8. **Search shortcut changed to Cmd/Ctrl+K**: docs search now uses the standard docs-site shortcut.

  9. **Page-level grid gap overrides**: pages can override the editorial grid gap through frontmatter, and generated OpenAPI pages use tighter spacing automatically.

  10. **Client-side routing is used consistently**: navigation links, configured links, footer links, MDX links, and TOC hash links now go through Spiceflow `Link` where appropriate.

  11. **Docs chat uses the hosted typed API**: chat requests go through the hosted Holocron API, with preserved model history for tool calls and local-development support for inline docs content.

  12. **Temporary AI fallback for previews**: preview docs can use a low-cost temporary model when no Holocron API key is configured.

  13. **New docs pages hot-reload in dev**: adding Markdown or MDX files now refreshes navigation without needing a server restart.

  14. **Better code highlighting for MDX snippets**: `mdx` fences now reuse Prism's Markdown grammar so nested fenced code blocks inside MDX examples get syntax highlighting.

  15. **Smaller server bundles for Mermaid sites**: Mermaid is resolved through an SSR stub and loaded only in the browser, reducing the SSR bundle for the real-world Polar fixture from about 7.55 MiB to 2.17 MiB.

  16. **Cleaner build output for Mermaid**: Mermaid's dynamic diagram dependencies are grouped into one lazy chunk instead of dozens of tiny files.

  17. **Table and layout polish**: Markdown tables use lighter row dividers, stay inside the content column, and preserve visible borders.

  18. **Sidebar and scrollbar polish**: navigation spacing, scrollbar thumbs, page breathing room, and border contrast were tuned for better readability in light and dark mode.

  19. **Fixed sidebar hydration state**: sidebars now use loader-provided route state for the first render, preventing hydration mismatches on non-default tabs.

  20. **Fixed active TOC tracking**: the active heading now updates when a section reaches the top reading position instead of using the viewport center.
</Update>

<Update id={"@holocron.so/vite@0.3.0"} label={"Apr 27, 2026"}>
  ## @holocron.so/vite\@0.3.0

  1. **OpenAPI auto-generated API reference pages** — add `"openapi": "spec.yaml"` to any navigation tab and Holocron processes the spec at build time, extracts all operations grouped by tag, and generates virtual pages with full endpoint documentation:

     ```json
     {
       "navigation": {
         "tabs": [
           { "tab": "Docs", "pages": ["index"] },
           { "tab": "API Reference", "openapi": "openapi.yaml" }
         ]
       }
     }
     ```

     Each endpoint page includes parameter tables, request/response bodies with JSON Schema types, cURL examples in a sticky right sidebar, and response code expandables.

  2. **Configurable `openapiBase` slug prefix** — control the URL prefix for generated OpenAPI pages (defaults to `"api"`). Set to `""` for no prefix:
     ```json
     { "tab": "API", "openapi": "spec.yaml", "openapiBase": "reference" }
     ```

  3. **Mermaid dark mode** — diagrams now re-render with the correct theme when toggling dark/light mode.

  4. **Auto-inject H1 from frontmatter title** — pages with a frontmatter `title` but no H1 in the body get a heading injected automatically.

  5. **Typography and layout refinements** — uniform 16px heading sizes, narrower content column for better readability, sidebar uses `text-sm` instead of `text-xs`.

  6. **Tabs component restyled** — uses `bg-accent` for cleaner tab panels.

  7. **OpenAPI field styling** — divider lines between fields, copy button on request examples, Mintlify-style rounded CodeCard containers.

  8. **Fixed `@tailwindcss/vite` and `tailwindcss` as dependencies** — were incorrectly in devDependencies causing missing styles in production.

  9. **Fixed OpenAPI active tab matching** and H1 filtering from TOC.
</Update>

<Update id={"@holocron.so/vite@0.2.0"} label={"Apr 25, 2026"}>
  ## @holocron.so/vite\@0.2.0

  1. **MDX import support** — import components from anywhere in your project using standard MDX import syntax. Components are discovered at build time and resolved at render time.

  2. **Auto-detect user global CSS** — Holocron automatically discovers and loads user global CSS files for smoother Mintlify migration.

  3. **AI Assistant control** — new `assistant.enabled` config field to disable the AI chat widget.

  4. **Default icon library switched from Lucide to FontAwesome** — use explicit `lucide:icon-name` syntax to keep using Lucide icons.

  5. **Wider content column with flexible grid** — tables and tabs now bleed properly with min-width 150px on table cells and horizontal scroll.

  6. **H3 headings** now use the same foreground color as h1/h2.

  7. **Unified vertical spacing** — Steps, lists, and containers now all use the `--prose-gap` token.

  8. **Component rename: `<Hero>` → `<Above>`** — update your MDX files if you use Hero directly.

  9. **Fixed phantom 48px gap** from empty first section in the editorial page grid.

  10. **Fixed imported components inside `<Above>`** — components in this section now render correctly.

  11. **Fixed sticky sidebar** — sidebar sticks below navbar even without a tab bar.

  12. **Fixed active TOC tracking** — improved heading highlight with 50% viewport threshold + hash change detection.

  13. **Fixed heading text rendering** — heading text no longer wrapped in prose-styled `<p>` element.

  14. **Fixed callout content** — no longer split incorrectly during MDX serialization.

  15. **Fixed spiceflow dual-instance crash** — deduplicated `@types/node` dependency.

  16. **Chat/AI fixes** — stale text, textarea preservation, store/hook boundary fixes.

  17. **Scrollbar gutter prevention** — `scrollbar-gutter: stable` prevents layout shift.

  18. **Performance** — shared pre-parsed mdast between module resolution and page rendering.

  19. **Improved sidebar link contrast** — opacity increased from 0.45 to 0.65.

  20. **Fixed implicit "Docs" tab visibility** — works correctly with versions + anchors.

  21. **Fixed nested index slugs** — loader titles resolve for nested directory pages.

  22. **Fixed tab indicator height and zustand import path**.
</Update>

<Update id={"@holocron.so/cli@0.6.0"} label={"Apr 25, 2026"}>
  ## @holocron.so/cli\@0.6.0

  1. **New `login`, `logout`, `whoami` commands** — authenticate with holocron.so via BetterAuth device flow. The CLI opens your browser, you approve, and the session token is saved locally:

     ```bash
     holocron login
     holocron whoami
     holocron logout
     ```

  2. **New `keys create`, `keys list`, `keys delete` commands** — manage API keys for deploying docs sites. Keys are scoped to your org and can authenticate the hosted AI proxy via `HOLOCRON_API_KEY`:

     ```bash
     holocron keys create --name production
     holocron keys list
     holocron keys delete <keyId>
     ```

  3. **Typed API client** — all API calls go through `spiceflow/client` with types auto-derived from the website routes and safe error handling via `errore` patterns.
</Update>

<Update id={"@holocron.so/vite@0.1.0"} label={"Apr 16, 2026"}>
  ## @holocron.so/vite\@0.1.0

  Initial release — drop-in Mintlify replacement as a Vite plugin.

  1. **Full Mintlify-compatible docs site from MDX** — reads `docs.json` (or `holocron.jsonc`) for navigation, tabs, groups, anchors, redirects, footer, banner, fonts, colors, SEO metadata, and favicon. Renders MDX pages with editorial typography, code blocks (Prism with all languages), callouts, tables, accordions, expandable fields, cards, steps, frames, panels, badges, tooltips, and more.

  2. **React Server Components on Vite 8** — powered by spiceflow. Server-rendered pages with full client hydration, client-side navigation, and per-page loaders.

  3. **Navigation with tabs, versions, and dropdowns** — `navigation.tabs` for switching sidebar content, `navigation.versions` for a version selector dropdown, and `navigation.dropdowns` (or `navigation.products`) for product-scoped navigation. Each switcher owns its own inner tab/group tree.

  4. **Custom entry point support** — mount holocron as a child of your own spiceflow app:
     ```ts
     import { createHolocronApp } from '@holocron.so/vite/app'
     const holocronApp = await createHolocronApp()
     const app = new Spiceflow().use(holocronApp)
     ```

  5. **HMR for config and MDX** — editing MDX content, adding/removing pages, and changing `docs.json` all hot-reload without a full page refresh.

  6. **AI agent support** — serves raw markdown at `/<page>.md` URLs, redirects AI user-agents to `.md` endpoints, exposes `/sitemap.xml` with `.md` hints, and bundles all docs as `/docs.zip`.

  7. **Built-in icon atlas** — resolves Lucide and Font Awesome icons at build time. Icons render inline as SVGs inheriting `currentColor`. Supports emoji, URL, and structured `{ name, library }` icon objects.

  8. **Image processing with pixelated placeholders** — local images get dimensions + compact WebP placeholders at build time. Blur-to-sharp transition and click-to-zoom via `react-medium-image-zoom`.

  9. **OG image generation** — auto-generates Open Graph PNG images per page using Takumi.

  10. **Generated fallback logo** — text-based logo PNG using Bagnard font, with light and dark variants.

  11. **Sidebar search** — Orama full-text index with keyboard navigation and wrap-around.

  12. **Dark mode** — class-based with cookie persistence and a blocking theme script (no flash). OS preference fallback.

  13. **CSS `@layer holocron`** — all styles wrapped in a CSS layer. Uses shadcn v2 CSS variable convention for full theme customization.

  14. **Redirects** — exact match, named parameters (`:id`), trailing wildcards (`*`). Query strings preserved. 301 status.

  15. **Base path support** — mount docs under a subpath like `/docs`.

  16. **Footer with socials** — logo, social icons, up to 4 link columns.

  17. **Banner** — dismissible top banner with MDX content and configurable colors.

  18. **Custom virtual modules** — override `virtual:holocron-config` and `virtual:holocron-pages` for programmatic control.

  19. **Sticky per-section asides** — `<Aside>` scoped to its section, `<Aside full>` spans multiple sections. `RequestExample`/`ResponseExample` auto-widen the sidebar.

  20. **404 page** — styled 404 inside the editorial layout with missing path, link home, and `noindex` meta.
</Update>

<Update id={"@holocron.so/cli@0.3.2"} label={"Sep 18, 2025"}>
  ## @holocron.so/cli\@0.3.2

  ### Patch Changes

  * export holocronjsonc type
</Update>

<Update id={"@holocron.so/cli@0.3.1"} label={"Sep 18, 2025"}>
  ## @holocron.so/cli\@0.3.1

  ### Patch Changes

  * fix double api api in client
</Update>


---
title: Authentication
url: "https://holocron.so/docs/api-docs/authentication.md"
description: How to get a Holocron API key and authenticate your API requests.
---

## Authentication

Every request to the Holocron API is authenticated with an **API key**. Keys are
scoped to a single project, so the key alone tells Holocron which project you are
acting on; you never pass a project ID separately.

API keys look like `holo_xxxxxxxx`. Send yours as a **Bearer token** in the
`Authorization` header on every request:

```bash
curl https://holocron.so/api/v0/projects \
  -H "Authorization: Bearer holo_xxxxxxxx"
```

The same key works across the whole API: the management endpoints
(`/api/v0/projects`, `/api/v0/me`), the deploy pipeline
(`/api/v0/deployments`), and the AI chat gateway (`POST /api/chat`).
A key only ever acts on its own project; it cannot read or modify other projects
in your org. Creating new projects and managing API keys requires signing in
with the CLI or dashboard.

<Warning>
  Treat API keys like passwords. Never commit them to git or embed them in
  client-side code. Read the key from an environment variable on the server, and
  create a separate key per environment so you can rotate them independently.
</Warning>

## Get an API key

You can create a key two ways: from the CLI or from the holocron.so dashboard.
Both produce the same `holo_xxx` key.

### From the CLI

First authenticate the CLI once with the device flow (opens your browser):

```bash
npx -y "@holocron.so/cli" login
```

If you do not have a project yet, create one:

```bash
npx -y "@holocron.so/cli" projects create --name "My Docs"
```

Then create a key scoped to that project. Run `keys create` without `--project`
to pick from a list, or pass the project ID directly:

```bash
npx -y "@holocron.so/cli" keys create --name production --project <projectId>
```

The command prints the key once. Copy it immediately; it is not shown again.

<Tip>
  List existing keys with `npx -y "@holocron.so/cli" keys list` and revoke one with
  `npx -y "@holocron.so/cli" keys delete <keyId>`.
</Tip>

### From the holocron.so dashboard

Sign in at [holocron.so](https://holocron.so), open your project, and go to its
**API keys** section. Create a key, give it a name (for example `production`), and
copy the `holo_xxx` value. The key is scoped to that project automatically.

## Use the key

Pass the key on every API call. The example below lists your projects:

```bash
export HOLOCRON_KEY=holo_xxxxxxxx

curl https://holocron.so/api/v0/projects \
  -H "Authorization: Bearer $HOLOCRON_KEY"
```

The same `HOLOCRON_KEY` environment variable is also what the CLI and CI use to
deploy without an interactive login. See [Deploy to Holocron](/docs/deploy/holocron)
for the full deploy flow.

The rest of this section is the full endpoint reference, generated from the
OpenAPI specification.


---
title: Get me
url: "https://holocron.so/api/get-api-v0-me.md"
description: "For a signed-in session, returns the user, all orgs they belong to, and projects per org. For a project-scoped API key, returns only the key's org and its single project (no user identity). For an org"
---

<Aside full>
  <RequestExample>
    ```bash title="cURL" lines=false
    curl -X GET "https://api.example.com/api/v0/me"
    ```
  </RequestExample>
</Aside>

<OpenAPIEndpoint {...{"method":"get","path":"/api/v0/me","summary":"Get me","description":"For a signed-in session, returns the user, all orgs they belong to, and projects per org. For a project-scoped API key, returns only the key's org and its single project (no user identity). For an org-scoped API key, returns all projects in that org.","parameters":[],"responses":[{"status":"200","description":"","schema":{"type":"object","required":["user","orgs"],"properties":{"user":{"description":"Null when authenticated with an API key.","anyOf":[{"type":"object","required":["name","email","image"],"properties":{"name":{"type":"string"},"email":{"type":"string"},"image":{"anyOf":[{"type":"string"},{"type":"null"}]}}},{"type":"null"}]},"orgs":{"type":"array","items":{"type":"object","required":["id","name","role","projects"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"role":{"type":"string"},"projects":{"type":"array","items":{"type":"object","required":["projectId","orgId","name","subdomain","currentDeploymentId","defaultBranch","githubOwner","githubRepo","source","externalId","createdAt","updatedAt"],"properties":{"projectId":{"type":"string"},"orgId":{"type":"string"},"name":{"type":"string"},"subdomain":{"anyOf":[{"type":"string"},{"type":"null"}]},"currentDeploymentId":{"anyOf":[{"type":"string"},{"type":"null"}]},"defaultBranch":{"anyOf":[{"type":"string"},{"type":"null"}]},"githubOwner":{"anyOf":[{"type":"string"},{"type":"null"}]},"githubRepo":{"anyOf":[{"type":"string"},{"type":"null"}]},"source":{"anyOf":[{"type":"string"},{"type":"null"}]},"externalId":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"number","description":"Unix epoch milliseconds."},"updatedAt":{"type":"number","description":"Unix epoch milliseconds."}}}}}}}}},"examples":[]},{"status":"default","description":"","examples":[]}],"security":[],"servers":[]}} />


---
title: List projects
url: "https://holocron.so/api/get-api-v0-projects.md"
description: "For a signed-in session, lists every project across all orgs the caller belongs to. For a project-scoped API key, lists only the key's own project. For an org-scoped API key, lists every project in th"
---

<Aside full>
  <RequestExample>
    ```bash title="cURL" lines=false
    curl -X GET "https://api.example.com/api/v0/projects"
    ```
  </RequestExample>
</Aside>

<OpenAPIEndpoint {...{"method":"get","path":"/api/v0/projects","summary":"List projects","description":"For a signed-in session, lists every project across all orgs the caller belongs to. For a project-scoped API key, lists only the key's own project. For an org-scoped API key, lists every project in that org.","parameters":[],"responses":[{"status":"200","description":"","schema":{"type":"object","required":["projects"],"properties":{"projects":{"type":"array","items":{"type":"object","required":["projectId","orgId","name","subdomain","currentDeploymentId","defaultBranch","githubOwner","githubRepo","source","externalId","createdAt","updatedAt","orgName"],"properties":{"projectId":{"type":"string"},"orgId":{"type":"string"},"name":{"type":"string"},"subdomain":{"anyOf":[{"type":"string"},{"type":"null"}]},"currentDeploymentId":{"anyOf":[{"type":"string"},{"type":"null"}]},"defaultBranch":{"anyOf":[{"type":"string"},{"type":"null"}]},"githubOwner":{"anyOf":[{"type":"string"},{"type":"null"}]},"githubRepo":{"anyOf":[{"type":"string"},{"type":"null"}]},"source":{"anyOf":[{"type":"string"},{"type":"null"}]},"externalId":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"number","description":"Unix epoch milliseconds."},"updatedAt":{"type":"number","description":"Unix epoch milliseconds."},"orgName":{"type":"string"}}}}}},"examples":[]},{"status":"default","description":"","examples":[]}],"security":[],"servers":[]}} />


---
title: Create deployment
url: "https://holocron.so/api/post-api-v0-deployments.md"
description: Create deployment
---

<Aside full>
  <RequestExample>
    ```bash title="cURL" lines=false
    curl -X POST "https://api.example.com/api/v0/deployments" \
      -H "Content-Type: application/json" \
      -d '{
      "files": [
        {
          "path": "string",
          "hash": "string"
        }
      ],
      "projectId": "string",
      "branch": "string",
      "preview": true,
      "name": "string",
      "basePath": "string"
    }'
    ```
  </RequestExample>
</Aside>

<OpenAPIEndpoint {...{"method":"post","path":"/api/v0/deployments","summary":"Create deployment","parameters":[],"requestBody":{"required":true,"contentType":"application/json","schema":{"type":"object","required":["files"],"properties":{"files":{"type":"array","description":"File paths with SHA-256 content hashes","items":{"type":"object","required":["path","hash"],"properties":{"path":{"type":"string","minLength":1,"maxLength":512},"hash":{"type":"string","minLength":64,"maxLength":64,"pattern":"^[a-f0-9]+$"}}}},"projectId":{"type":"string","description":"Required for session auth; ignored for API key auth"},"branch":{"type":"string","description":"Branch name for preview deployments. Defaults to \"main\".","maxLength":200},"preview":{"type":"boolean","description":"Force preview deployment (e.g. from a PR). Never updates production pointer."},"name":{"type":"string","description":"Site name from docs.json. Updates the project name if provided.","minLength":1,"maxLength":200},"basePath":{"type":"string","description":"Base path prefix for subpath deploys (e.g. \"/docs/\"). Requires Pro subscription.","maxLength":200,"pattern":"^\\/[a-z0-9\\-_/]*\\/$"}}},"examples":[]},"responses":[{"status":"200","description":"","schema":{"type":"object","required":["deploymentId","version","existingHashes"],"properties":{"deploymentId":{"type":"string"},"version":{"type":"string"},"existingHashes":{"type":"array","description":"Hashes that already exist in KV — CLI can skip uploading these files","items":{"type":"string"}}}},"examples":[]},{"status":"default","description":"","examples":[]}],"security":[],"servers":[]}} />


---
title: Upload files
url: "https://holocron.so/api/put-api-v0-deployments-deploymentid-files.md"
description: Upload files
---

<Aside full>
  <RequestExample>
    ```bash title="cURL" lines=false
    curl -X PUT "https://api.example.com/api/v0/deployments/<deploymentId>/files"
    ```
  </RequestExample>
</Aside>

<OpenAPIEndpoint {...{"method":"put","path":"/api/v0/deployments/{deploymentId}/files","summary":"Upload files","parameters":[{"name":"deploymentId","in":"path","required":true,"schema":{"type":"string"}}],"responses":[{"status":"200","description":"","schema":{"type":"object","required":["uploaded"],"properties":{"uploaded":{"type":"number"}}},"examples":[]},{"status":"default","description":"","examples":[]}],"security":[],"servers":[]}} />


---
title: Finalize deployment
url: "https://holocron.so/api/post-api-v0-deployments-deploymentid-finalize.md"
description: Finalize deployment
---

<Aside full>
  <RequestExample>
    ```bash title="cURL" lines=false
    curl -X POST "https://api.example.com/api/v0/deployments/<deploymentId>/finalize"
    ```
  </RequestExample>
</Aside>

<OpenAPIEndpoint {...{"method":"post","path":"/api/v0/deployments/{deploymentId}/finalize","summary":"Finalize deployment","parameters":[{"name":"deploymentId","in":"path","required":true,"schema":{"type":"string"}}],"responses":[{"status":"200","description":"","schema":{"type":"object","required":["url","deploymentId","branch"],"properties":{"url":{"type":"string"},"deploymentId":{"type":"string"},"branch":{"type":"string"}}},"examples":[]},{"status":"default","description":"","examples":[]}],"security":[],"servers":[]}} />


---
title: Add custom domain
url: "https://holocron.so/api/post-api-v0-domains.md"
description: Register a custom domain for a deployed project. Requires a Pro subscription or a partner org. The domain must be CNAMEd to cname.holocron.so for SSL provisioning.
---

<Aside full>
  <RequestExample>
    ```bash title="cURL" lines=false
    curl -X POST "https://api.example.com/api/v0/domains" \
      -H "Content-Type: application/json" \
      -d '{
      "projectId": "string",
      "hostname": "string"
    }'
    ```
  </RequestExample>
</Aside>

<OpenAPIEndpoint {...{"method":"post","path":"/api/v0/domains","summary":"Add custom domain","description":"Register a custom domain for a deployed project. Requires a Pro subscription or a partner org. The domain must be CNAMEd to cname.holocron.so for SSL provisioning.","parameters":[],"requestBody":{"required":true,"contentType":"application/json","schema":{"type":"object","required":["projectId","hostname"],"properties":{"projectId":{"type":"string","description":"Project ULID.","minLength":1},"hostname":{"type":"string","description":"Custom domain hostname (e.g. docs.mycompany.com).","minLength":1,"maxLength":253}}},"examples":[]},"responses":[{"status":"200","description":"","schema":{"type":"object","required":["id","hostname","status","sslStatus","cnameTarget","createdAt"],"properties":{"id":{"type":"string"},"hostname":{"type":"string"},"status":{"type":"string"},"sslStatus":{"anyOf":[{"type":"string"},{"type":"null"}]},"cnameTarget":{"type":"string","description":"CNAME target for DNS configuration."},"createdAt":{"type":"number"}}},"examples":[]},{"status":"400","description":"","schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"examples":[]},{"status":"402","description":"","schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"examples":[]},{"status":"409","description":"","schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"examples":[]},{"status":"default","description":"","examples":[]}],"security":[],"servers":[]}} />


---
title: List custom domains
url: "https://holocron.so/api/get-api-v0-domains-projectid.md"
description: List all custom domains for a project.
---

<Aside full>
  <RequestExample>
    ```bash title="cURL" lines=false
    curl -X GET "https://api.example.com/api/v0/domains/<projectId>"
    ```
  </RequestExample>
</Aside>

<OpenAPIEndpoint {...{"method":"get","path":"/api/v0/domains/{projectId}","summary":"List custom domains","description":"List all custom domains for a project.","parameters":[{"name":"projectId","in":"path","required":true,"description":"Project ULID.","schema":{"type":"string"}}],"responses":[{"status":"200","description":"","schema":{"type":"object","required":["domains"],"properties":{"domains":{"type":"array","items":{"type":"object","required":["id","hostname","status","sslStatus","cnameTarget","createdAt"],"properties":{"id":{"type":"string"},"hostname":{"type":"string"},"status":{"type":"string"},"sslStatus":{"anyOf":[{"type":"string"},{"type":"null"}]},"cnameTarget":{"type":"string","description":"CNAME target for DNS configuration."},"createdAt":{"type":"number"}}}}}},"examples":[]},{"status":"default","description":"","examples":[]}],"security":[],"servers":[]}} />


---
title: Check domain status
url: "https://holocron.so/api/get-api-v0-domains-projectid-domainid-status.md"
description: "Fetch the latest status from Cloudflare and update the local record. When the domain becomes active, the KV mapping is written so the hosting worker starts serving traffic."
---

<Aside full>
  <RequestExample>
    ```bash title="cURL" lines=false
    curl -X GET "https://api.example.com/api/v0/domains/<projectId>/<domainId>/status"
    ```
  </RequestExample>
</Aside>

<OpenAPIEndpoint {...{"method":"get","path":"/api/v0/domains/{projectId}/{domainId}/status","summary":"Check domain status","description":"Fetch the latest status from Cloudflare and update the local record. When the domain becomes active, the KV mapping is written so the hosting worker starts serving traffic.","parameters":[{"name":"projectId","in":"path","required":true,"description":"Project ULID.","schema":{"type":"string"}},{"name":"domainId","in":"path","required":true,"description":"Domain ULID.","schema":{"type":"string"}}],"responses":[{"status":"200","description":"","schema":{"type":"object","required":["id","hostname","status","sslStatus","cnameTarget","createdAt"],"properties":{"id":{"type":"string"},"hostname":{"type":"string"},"status":{"type":"string"},"sslStatus":{"anyOf":[{"type":"string"},{"type":"null"}]},"cnameTarget":{"type":"string","description":"CNAME target for DNS configuration."},"createdAt":{"type":"number"}}},"examples":[]},{"status":"404","description":"","schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"examples":[]},{"status":"default","description":"","examples":[]}],"security":[],"servers":[]}} />


---
title: Remove custom domain
url: "https://holocron.so/api/delete-api-v0-domains-projectid-domainid.md"
description: Delete a custom domain from Cloudflare and the database.
---

<Aside full>
  <RequestExample>
    ```bash title="cURL" lines=false
    curl -X DELETE "https://api.example.com/api/v0/domains/<projectId>/<domainId>"
    ```
  </RequestExample>
</Aside>

<OpenAPIEndpoint {...{"method":"delete","path":"/api/v0/domains/{projectId}/{domainId}","summary":"Remove custom domain","description":"Delete a custom domain from Cloudflare and the database.","parameters":[{"name":"projectId","in":"path","required":true,"description":"Project ULID.","schema":{"type":"string"}},{"name":"domainId","in":"path","required":true,"description":"Domain ULID.","schema":{"type":"string"}}],"responses":[{"status":"200","description":"","schema":{"type":"object","required":["deleted"],"properties":{"deleted":{"type":"boolean"}}},"examples":[]},{"status":"404","description":"","schema":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"examples":[]},{"status":"default","description":"","examples":[]}],"security":[],"servers":[]}} />


---
title: Get OpenAPI specification
url: "https://holocron.so/api/get-openapijson.md"
description: Returns the OpenAPI specification document for this API in JSON format.
---

<Aside full>
  <RequestExample>
    ```bash title="cURL" lines=false
    curl -X GET "https://api.example.com/openapi.json"
    ```
  </RequestExample>
</Aside>

<OpenAPIEndpoint {...{"method":"get","path":"/openapi.json","summary":"Get OpenAPI specification","description":"Returns the OpenAPI specification document for this API in JSON format.","parameters":[],"responses":[{"status":"200","description":"OpenAPI specification document","schema":{"type":"object","properties":{"openapi":{"type":"string","description":"OpenAPI version","example":"3.1.3"},"info":{"type":"object","properties":{"title":{"type":"string"},"description":{"type":"string"},"version":{"type":"string"}}},"paths":{"type":"object","description":"Available API endpoints"},"components":{"type":"object","description":"Reusable schema components"}}},"examples":[]}],"security":[],"servers":[]}} />
