Deployment & Hosting — Quartz 4 on GitHub Pages

1. Executive Summary

This plan implements the web publishing pipeline for the Calab.ai Handbook, rendering the Obsidian vault content at content/ as a static website using Quartz 4, deployed to GitHub Pages.

The approach uses a single-repository model: the existing calab-handbook repository contains both the Obsidian vault content and the Quartz build system. Publishing is git-native — content changes are committed, pushed (or merged via PR), and automatically built and deployed by GitHub Actions.

Key outcomes:

  • Vault content rendered as a searchable, themed static website with graph view, backlinks, and full-text search
  • Obsidian content features preserved (callouts, wikilinks, Mermaid diagrams, tags, syntax highlighting, transclusions)
  • Zero-cost hosting on GitHub Pages with automated CI/CD
  • Selective publishing via publish: true frontmatter — unpublished content remains private
  • Full governance alignment: publishing goes through git workflow (PRs, CODEOWNERS, branch protection, Entra ID team integration)
  • No Obsidian plugin dependency — no PAT token management, no token expiry risk

Critical dependencies:

  • GitHub organisation admin access (calab-ai)
  • Node.js v22+ (for local preview builds; GitHub Actions handles production builds)
  • Git access for contributors (CLI, GitHub Desktop, or VS Code)

Timeline: 2–3 weeks for base platform (Steps 0–2). Content migration and enhancement in subsequent steps.

Governing decisions:


2. Architecture / Context Overview

Single-Repository Architecture

┌──────────────────────────────────┐       ┌─────────────┐
│  calab-ai/calab-handbook          │       │  GitHub     │
│  (Single Repository)              │──────▶│  Pages      │
│                                   │  GH   │  (Live Site)│
│  content/            (content)      │Actions│             │
│  quartz/           (build engine) │       │             │
│  quartz.config.ts  (site config)  │       │             │
│  quartz.layout.ts  (layout)       │       │             │
│  public/           (build output) │       │             │
│  .github/workflows/deploy.yml     │       │             │
│  docs/             (plans, decisions)  │       │             │
└──────────────────────────────────┘       └─────────────┘

How publishing works:

  1. Author adds publish: true to a note’s frontmatter in the vault
  2. Author commits the change and pushes to a feature branch
  3. Author opens a Pull Request for review (governed by CODEOWNERS and branch protection)
  4. On PR merge to main, GitHub Actions triggers: npm cinpx quartz build → deploy public/ to GitHub Pages via artifacts
  5. Site is live within 2–4 minutes

Key difference from Digital Garden (v1.0 plan): No second repository, no Obsidian plugin dependency, no PAT token, no governance bypass. Publishing is entirely git-native.

Current Vault Structure (Post-Plan 01)

content/
├── 00 Governance/          # 6 governance docs
│   ├── 01 Operating Model.md
│   ├── 01 How to Contribute.md
│   ├── 02 Knowledge Standards.md
│   ├── 02 Operational Governance.md
│   ├── 03 Decision Records.md
│   └── 04 Org Structure & Roles.md
├── 01 Value Streams/               # 7 value streams (VS01–VS07)
│   ├── README.md                    # Value Stream Index
│   └── VS01–VS07 (each with README + 5 docs)
├── 02 Guilds/                      # 5 guilds (GL01–GL05)
│   ├── README.md                    # Guild Index
│   └── GL01–GL05 (each with README + Practices/)
├── 03 Products/            # Product documentation
│   └── NeuralOps/ (README, Architecture, Technical Planning, decisions)
├── 99 Archive/                     # Deprecated content (DO NOT publish)
├── metadata/                       # Content Types, Tags, Navigation, Glossary, templates, diagrams
│   ├── diagrams/                   # Excalidraw/draw.io source files
│   ├── glossary/                   # Glossary definitions
│   ├── templater/                  # TMP00–TMP05 templates
│   ├── faq/                        # FAQ content
│   ├── Content Types.md
│   ├── Glossary.md
│   ├── Navigation.md
│   └── Tags.md
├── README.md                       # Vault root (currently empty)
└── .obsidian/                      # Plugin/theme configuration

Key constraint: The metadata/ directory and 99 Archive/ contain internal vault tooling and must be excluded from publishing. Quartz handles this via ignorePatterns in quartz.config.ts — these directories are completely skipped during content parsing.

Technology Stack

ComponentTechnologyVersion
Static Site GeneratorQuartz 4v4.5.x
RuntimeNode.js22.x
LanguageTypeScript / JSXLatest
StylingSCSSLatest
Content FormatObsidian-Flavored Markdown
HostingGitHub Pages
CI/CDGitHub Actions

3. Implementation Steps

Step 0 — Prerequisites & Repository Cleanup

Goal: Prepare the repository for Quartz installation by cleaning up legacy artefacts and updating configuration.

Tasks

0.1 Clean Up .obsidian/ Directory

The vault’s .obsidian/ directory contains ~60+ duplicate/versioned config files (e.g., workspace 17.json, appearance 14.json, community-plugins 7.json). These are Obsidian sync artefacts and should be cleaned up.

  1. Back up the .obsidian/ directory
  2. Identify the current/active config files (no number suffix): workspace.json, app.json, appearance.json, community-plugins.json, core-plugins.json
  3. Delete all numbered duplicates (e.g., workspace 17.json, appearance 14.json, etc.)
  4. Delete .DS_Store files

0.2 Update .gitignore

Current .gitignore excludes .obsidian entirely (line 1). Replace with a Quartz-aware configuration:

# Quartz build output
public/
.quartz-cache/
 
# Node.js
node_modules/
 
# Obsidian - exclude personal workspace files
content/.obsidian/workspace*.json
content/.obsidian/*.json.bak
content/.obsidian/plugins/*/data.json
content/.obsidian/.DS_Store
 
# Obsidian - these ARE tracked (for reference):
# content/.obsidian/appearance.json
# content/.obsidian/community-plugins.json
# content/.obsidian/core-plugins.json
# content/.obsidian/app.json
 
# Exclude OS files
.DS_Store
Thumbs.db
 
# Legacy
_templates/00 Vault Configs/__MACOSX
!_templates/**.zip

Stage and commit the newly-visible .obsidian/ config files: appearance.json, community-plugins.json, core-plugins.json, app.json.

0.3 Remove Digital Garden Plugin Reference

Remove "digitalgarden" from content/.obsidian/community-plugins.json. Also delete the content/.obsidian/plugins/digitalgarden/ directory if it exists.

0.4 Clean Up Existing gh-pages Branch

The gh-pages branch already exists with 4 legacy commits and a CNAME file. This will conflict with the new GitHub Pages deployment.

  1. Delete the existing gh-pages branch: git push origin --delete gh-pages
  2. Delete the local branch: git branch -D gh-pages
  3. The new GitHub Actions workflow uses actions/deploy-pages (artifact-based deployment), not a branch

Deliverable

Clean repository ready for Quartz installation: no duplicate .obsidian/ files, correct .gitignore, no legacy branch conflicts, Digital Garden plugin removed.

Verification

  • git status shows .obsidian/ config files are tracked (not ignored)
  • gh-pages branch deleted (local and remote)
  • digitalgarden no longer in community-plugins.json
  • .gitignore includes public/, node_modules/, .quartz-cache/

Step 1 — Install Quartz 4

Goal: Install the Quartz 4 build system into the existing repository alongside the vault content.

Tasks

1.1 Clone Quartz to a Temporary Location

# Clone Quartz 4 into a temp directory
git clone --branch v4 --depth 1 https://github.com/jackyzha0/quartz.git /tmp/quartz-temp

1.2 Copy Quartz Scaffolding into the Repository

Copy the necessary Quartz files into the repository root. The vault content lives in content/ — this is Quartz’s default content directory.

# From the repository root
cp -r /tmp/quartz-temp/quartz ./quartz
cp /tmp/quartz-temp/quartz.config.ts ./quartz.config.ts
cp /tmp/quartz-temp/quartz.layout.ts ./quartz.layout.ts
cp /tmp/quartz-temp/package.json ./package.json
cp /tmp/quartz-temp/package-lock.json ./package-lock.json
cp /tmp/quartz-temp/tsconfig.json ./tsconfig.json
 
# Clean up temp clone
rm -rf /tmp/quartz-temp

1.3 Add Quartz Upstream Remote

This allows pulling future Quartz updates:

git remote add upstream https://github.com/jackyzha0/quartz.git

1.4 Install Node Dependencies

npm ci

1.5 Configure quartz.config.ts

Replace the default quartz.config.ts with the Calab.ai configuration:

import { QuartzConfig } from "./quartz/cfg";
import * as Plugin from "./quartz/plugins";
 
const config: QuartzConfig = {
  configuration: {
    pageTitle: "Calab.ai Handbook",
    pageTitleSuffix: " | Calab.ai",
    enableSPA: true,
    enablePopovers: true,
    analytics: null,
    locale: "en-AU",
    baseUrl: "handbook.calab.ai", // UPDATE: set to your actual domain or GitHub Pages URL
    ignorePatterns: [
      "metadata/diagrams/canvas",
      "metadata/diagrams/drawio",
      "metadata/faq",
      "metadata/templater",
      "metadata/Content Types.md",
      "metadata/Navigation.md",
      "metadata/Tags.md",
      "metadata/Glossary.md",
      "**/Practices/*/metadata/**",
      "99 Archive",
      ".obsidian",
    ],
    defaultDateType: "modified",
    theme: {
      fontOrigin: "googleFonts",
      cdnCaching: true,
      typography: {
        header: "Schibsted Grotesk",
        body: "Source Sans Pro",
        code: "IBM Plex Mono",
      },
      colors: {
        lightMode: {
          light: "#faf8f8",
          lightgray: "#e5e5e5",
          gray: "#b8b8b8",
          darkgray: "#4e4e4e",
          dark: "#2b2b2b",
          secondary: "#284b63",
          tertiary: "#84a59d",
          highlight: "rgba(143, 159, 169, 0.15)",
          textHighlight: "#fff23688",
        },
        darkMode: {
          light: "#161618",
          lightgray: "#393639",
          gray: "#646464",
          darkgray: "#d4d4d4",
          dark: "#ebebec",
          secondary: "#7b97aa",
          tertiary: "#84a59d",
          highlight: "rgba(143, 159, 169, 0.15)",
          textHighlight: "#b3aa0288",
        },
      },
    },
  },
  plugins: {
    transformers: [
      Plugin.FrontMatter(),
      Plugin.CreatedModifiedDate({
        priority: ["frontmatter", "git", "filesystem"],
      }),
      Plugin.SyntaxHighlighting({
        theme: {
          light: "github-light",
          dark: "github-dark",
        },
        keepBackground: false,
      }),
      Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }),
      Plugin.GitHubFlavoredMarkdown(),
      Plugin.TableOfContents(),
      Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
      Plugin.Description(),
      Plugin.Latex({ renderEngine: "katex" }),
    ],
    filters: [Plugin.ExplicitPublish()],
    emitters: [
      Plugin.AliasRedirects(),
      Plugin.ComponentResources(),
      Plugin.ContentPage(),
      Plugin.FolderPage(),
      Plugin.TagPage(),
      Plugin.ContentIndex({
        enableSiteMap: true,
        enableRSS: true,
      }),
      Plugin.Assets(),
      Plugin.Static(),
      Plugin.Favicon(),
      Plugin.NotFoundPage(),
    ],
  },
};
 
export default config;

Key configuration choices:

  • filters: [Plugin.ExplicitPublish()] — Only notes with publish: true in frontmatter are published. This replaces the default Plugin.RemoveDrafts().
  • ignorePatterns — Completely excludes metadata/ subdirectories, 99 Archive/, .obsidian/, and practice-level metadata/ folders from parsing.
  • locale: "en-AU" — Australian English for date formatting.
  • baseUrl — Must be updated to the actual deployment URL (see Decision Point 1).

1.6 Configure quartz.layout.ts

Update the footer to reference Calab.ai:

import { PageLayout, SharedLayout } from "./quartz/cfg";
import * as Component from "./quartz/components";
 
export const sharedPageComponents: SharedLayout = {
  head: Component.Head(),
  header: [],
  afterBody: [],
  footer: Component.Footer({
    links: {
      "Calab.ai": "https://calab.ai",
      GitHub: "https://github.com/calab-ai/calab-handbook",
    },
  }),
};
 
export const defaultContentPageLayout: PageLayout = {
  beforeBody: [
    Component.ConditionalRender({
      component: Component.Breadcrumbs(),
      condition: (page) => page.fileData.slug !== "index",
    }),
    Component.ArticleTitle(),
    Component.ContentMeta(),
    Component.TagList(),
  ],
  left: [
    Component.PageTitle(),
    Component.MobileOnly(Component.Spacer()),
    Component.Flex({
      components: [
        {
          Component: Component.Search(),
          grow: true,
        },
        { Component: Component.Darkmode() },
        { Component: Component.ReaderMode() },
      ],
    }),
    Component.Explorer(),
  ],
  right: [
    Component.Graph(),
    Component.DesktopOnly(Component.TableOfContents()),
    Component.Backlinks(),
  ],
};
 
export const defaultListPageLayout: PageLayout = {
  beforeBody: [
    Component.Breadcrumbs(),
    Component.ArticleTitle(),
    Component.ContentMeta(),
  ],
  left: [
    Component.PageTitle(),
    Component.MobileOnly(Component.Spacer()),
    Component.Flex({
      components: [
        {
          Component: Component.Search(),
          grow: true,
        },
        { Component: Component.Darkmode() },
      ],
    }),
    Component.Explorer(),
  ],
  right: [],
};

1.7 Verify Local Build

npx quartz build --serve

Open http://localhost:8080/ to verify the site builds and renders. At this point, no content will appear because no notes have publish: true yet — this is expected.

Deliverable

Quartz 4 installed and configured in the repository. Local build succeeds.

Verification

  • npx quartz build completes without errors
  • public/ directory is generated with HTML output
  • package.json exists at repository root with Quartz dependencies
  • quartz.config.ts has Calab.ai-specific configuration
  • quartz/ directory contains the Quartz build engine

Step 2 — GitHub Actions & Pages Setup

Goal: Set up automated deployment to GitHub Pages.

Tasks

2.1 Create GitHub Actions Workflow

Create .github/workflows/deploy.yml:

name: Deploy Quartz site to GitHub Pages
 
on:
  push:
    branches:
      - main
 
permissions:
  contents: read
  pages: write
  id-token: write
 
concurrency:
  group: "pages"
  cancel-in-progress: false
 
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0 # Fetch all history for git timestamps
 
      - uses: actions/setup-node@v4
        with:
          node-version: 22
 
      - name: Install Dependencies
        run: npm ci
 
      - name: Build Quartz
        run: npx quartz build
 
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: public
 
  deploy:
    needs: build
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4

Note: fetch-depth: 0 is important — Quartz uses git history for CreatedModifiedDate timestamps. Without full history, all dates will show as the checkout date.

2.2 Configure GitHub Pages

In the calab-ai/calab-handbook repository settings:

  1. Go to Settings → Pages
  2. Set Source to “GitHub Actions” (NOT “Deploy from a branch”)
  3. If an existing GitHub Pages environment exists, delete it first (Settings → Environments → trash icon) — the workflow will recreate it

2.3 Configure Custom Domain (Decision Point 1)

See Decision Points section below. If using a custom domain:

  1. Settings → Pages → Custom Domain → enter domain (e.g., handbook.calab.ai)
  2. Configure DNS:
    • For subdomain: Create CNAME record pointing to calab-ai.github.io
    • For apex domain: Create A records pointing to GitHub’s IPs (185.199.108.153, 185.199.109.153, 185.199.110.153, 185.199.111.153)
  3. Enable “Enforce HTTPS”

If using a custom domain, add a CNAME plugin to Quartz. In quartz.config.ts, ensure the baseUrl matches the custom domain.

2.4 Push and Verify

git add .
git commit -m "feat: install Quartz 4 build system and GitHub Actions deployment"
git push origin main
  1. Check GitHub Actions tab — the deploy.yml workflow should trigger
  2. Wait for build + deploy jobs to complete (2–4 minutes)
  3. Visit the GitHub Pages URL — should show a Quartz site with no content (since no notes have publish: true yet)

Deliverable

GitHub Actions workflow deployed and GitHub Pages serving the site.

Verification

  • GitHub Actions workflow completes successfully (green check)
  • GitHub Pages settings show “GitHub Actions” as source
  • Site is accessible at the Pages URL (even if empty)
  • Workflow uses the default content/ directory to build from vault content

Step 3 — Pilot Test with Home Page & Sample Content

Goal: Verify end-to-end publishing pipeline with real content before bulk migration.

Tasks

3.1 Create Home Page

Quartz uses index.md in the content directory as the site root. Create content/index.md:

---
title: Calab.ai Handbook
publish: true
---
 
# Calab.ai Handbook
 
Welcome to the Calab.ai internal knowledge repository.
 
## Quick Links
 
- [[00 Governance/01 Operating Model|Operating Model]]
- [[01 Value Streams/README|Value Streams]]
- [[02 Guilds/README|Guilds]]
- [[03 Products/NeuralOps/README|NeuralOps]]
 
## Getting Started
 
New team members should start with:
 
1. [[00 Governance/01 Operating Model|Company Operating Model]]
2. [[00 Governance/01 How to Contribute|How to Contribute]]
3. [[02 Guilds/README|Guild & Practice Overview]]
 
Use search (Ctrl+K) to find specific topics.

3.2 Add publish: true to Pilot Pages

Select 10 pages that exercise different content types and linking patterns. Add publish: true to the frontmatter of each:

Governance (3 pages):

  • content/00 Governance/01 Operating Model.md
  • content/00 Governance/01 How to Contribute.md
  • content/00 Governance/02 Knowledge Standards.md

Value Streams (2 pages):

  • content/01 Value Streams/README.md
  • content/01 Value Streams/VS01 Lead to Cash/README.md

Guilds (3 pages):

  • content/02 Guilds/README.md
  • content/02 Guilds/GL04 Technology Guild/README.md
  • content/02 Guilds/GL05 Administration Guild/Practices/HR Management/README.md

Products (1 page):

  • content/03 Products/NeuralOps/02 Architecture.md

For each, add to the frontmatter (or create frontmatter if none exists):

---
publish: true
---

3.3 Commit and Deploy

git add content/index.md content/00\ Company\ Governance/ content/01\ Value\ Streams/ content/02\ Guilds/ content/03\ Company\ Products/
git commit -m "feat: add publish frontmatter to pilot pages and create home page"
git push origin main

3.4 Verify Pilot Deployment

  • Home page renders at site root URL
  • All 10 pilot pages are accessible
  • Internal wikilinks between published pages resolve correctly
  • Links to unpublished pages are dimmed/disabled (not 500 errors)
  • File tree (Explorer) shows correct folder hierarchy for published pages
  • Search (Ctrl+K) finds pilot content
  • Table of contents appears on long pages
  • Graph view displays connections between published pages
  • Backlinks section shows incoming links
  • Breadcrumbs show correct path
  • Dark mode toggle works
  • Mobile/responsive layout works

3.5 Troubleshooting Checklist

If the build fails:

  • Check GitHub Actions build logs for errors
  • Verify quartz.config.ts syntax is valid (run npx quartz build locally)
  • Ensure content/index.md exists and has publish: true
  • Check that ignorePatterns don’t accidentally exclude content directories

If the site shows 404:

  • Verify GitHub Pages source is set to “GitHub Actions”
  • Delete any existing github-pages environment and let the workflow recreate it
  • Check baseUrl matches the actual deployment URL

If styles/assets are broken:

  • Verify baseUrl in quartz.config.ts is correct
  • If using a project site (not custom domain), ensure baseUrl includes the repo name path

Go/No-Go Decision:

  • ✅ Home page + pilot pages render correctly → Proceed to Step 4
  • ❌ Pipeline fails → Resolve before continuing

Deliverable

Working end-to-end publishing pipeline verified with a live home page and 10 pilot pages.

Verification

  • Home page visible at the site URL
  • All verification checklist items in 3.4 pass
  • No console errors on the live site
  • GitHub Actions build completes in < 5 minutes

Step 4 — Excalidraw SVG Export Workflow

Goal: Enable Excalidraw diagrams to display on the published site via SVG auto-export.

Tasks

4.1 Enable Auto-Export in Excalidraw Plugin

In Obsidian:

  1. Settings → Excalidraw
  2. Under Export Settings, enable: Auto-export SVG
  3. Set export path to same folder as the .excalidraw.md file (default)
  4. This will automatically generate .svg files alongside every .excalidraw.md file when edited

4.2 Embed SVGs in Published Notes

Instead of embedding Excalidraw files directly using Obsidian wikilink syntax, use standard Markdown image syntax with the SVG export:

![diagram description](diagram.svg)

For existing notes that embed Excalidraw files, update to standard Markdown image syntax ![alt](path) referencing the .svg version.

4.3 Ensure SVGs Are Not Ignored

Verify that ignorePatterns in quartz.config.ts doesn’t exclude .svg files. The current config excludes the metadata/diagrams/ subdirectories — if diagrams are stored there, the SVGs should instead be colocated with the notes that reference them, or the embedding notes should be in a non-ignored directory.

Recommended approach:

  • Keep .excalidraw.md source files in metadata/diagrams/ (excluded from publishing)
  • Export SVGs to the same directory as the notes that embed them
  • Or create a content/assets/ directory for shared SVG exports

4.4 Test Diagram Rendering

  1. Open an existing Excalidraw file in Obsidian (e.g., SG02 Organisational Reporting Structure.excalidraw.md)
  2. Verify the auto-export creates a corresponding .svg file
  3. Create a test note with publish: true that embeds the SVG
  4. Commit, push, and verify the diagram renders on the live site

Deliverable

Excalidraw diagrams render on the published site via SVG auto-export.

Verification

  • SVG files are auto-generated when Excalidraw files are edited
  • Embedded SVGs display correctly on the published site
  • Diagrams scale properly and are readable at different viewport sizes

Step 5 — Content Migration & Bulk Publishing

Goal: Systematically publish all appropriate vault content.

Tasks

5.1 Content Audit

Create a content inventory:

FolderNote CountPriorityContains Sensitive Data?Status
00 Governance/6HighNoNot Started
01 Value Streams/ (all VS)~50+HighNoNot Started
02 Guilds/ (all GL)~30+HighNoNot Started
03 Products/NeuralOps/4+MediumReview neededNot Started

Exclude from publishing (handled by ignorePatterns):

  • 99 Archive/ — deprecated content
  • metadata/templater/ — internal templates
  • metadata/ — internal taxonomy (individual files/subdirectories excluded)
  • metadata/diagrams/ — raw diagram source files (embed SVG exports in published notes instead)
  • **/Practices/*/metadata/** — practice-level metadata subdirectories

5.2 Batch Frontmatter Addition

For all notes to be published, add publish: true to frontmatter.

Script approach (run from content/ directory):

# BACKUP FIRST: git stash or commit current state
 
# Add publish: true to all .md files in numbered directories
# that don't already have publish in their frontmatter
for dir in "00 Governance" "01 Value Streams" "02 Guilds" "03 Products"; do
  find "$dir" -name "*.md" -type f | while read file; do
    if ! head -n 5 "$file" | grep -q "publish"; then
      if head -n 1 "$file" | grep -q "^---$"; then
        # Has frontmatter — insert publish: true after first ---
        sed -i '1a publish: true' "$file"
      else
        # No frontmatter — prepend it
        temp=$(mktemp)
        printf '%s\n' "---" "publish: true" "---" "" | cat - "$file" > "$temp"
        mv "$temp" "$file"
      fi
    fi
  done
done

Manual approach: Use Obsidian’s Properties panel to add publish: true to each note individually.

5.3 Phased Publishing

Week 1 — High Priority:

  • All 00 Governance/ docs
  • 01 Value Streams/ README and Index
  • 02 Guilds/ README, Guild Index, and all Guild READMEs
  • Home page (already done in Step 3)
  • Target: ~20 pages

Week 2 — Medium Priority:

  • All Value Stream sub-pages (01–06 in each VS)
  • All Practice READMEs
  • GL05 HR Management sub-content (policies, templates)
  • Target: ~40 pages

Week 3 — Remaining:

  • Product documentation (NeuralOps)
  • Any remaining guild/practice content
  • Target: All remaining approved content

5.4 Commit and Deploy

git add content/
git commit -m "feat: add publish frontmatter to [batch description]"
git push origin main

Or, for governance compliance, create a feature branch and PR:

git checkout -b content/publish-governance-docs
git add content/00\ Company\ Governance/
git commit -m "feat: mark governance docs for publishing"
git push -u origin content/publish-governance-docs
gh pr create --title "Publish governance documentation" --body "Marks all 00 Governance docs with publish: true frontmatter"

5.5 Link Validation

After bulk publish, verify links:

# From a local machine, run a broken link checker against the live site
npx broken-link-checker https://YOUR-SITE-URL --recursive --ordered

Or use Quartz’s local build to check for warnings:

npx quartz build --verbose 2>&1 | grep -i "warn"

Deliverable

All appropriate vault content published with valid links and correct formatting.

Verification

  • All target pages published (check site file tree / explorer)
  • Broken link checker reports zero or minimal broken links
  • File tree on the site reflects the expected folder hierarchy
  • Search indexes all published content

Step 6 — Site Customisation & Branding

Goal: Customise the site appearance to match Calab.ai branding.

Tasks

6.1 Configure Theme Colors in quartz.config.ts

Update the colors section in quartz.config.ts to match Calab.ai brand guidelines. The default Quartz theme is a good starting point — adjust secondary (link colour), tertiary (hover states), and other slots as needed.

colors: {
  lightMode: {
    light: "#faf8f8",       // page background
    lightgray: "#e5e5e5",   // borders
    gray: "#b8b8b8",        // graph links, heavier borders
    darkgray: "#4e4e4e",    // body text
    dark: "#2b2b2b",        // header text and icons
    secondary: "#284b63",   // link colour — UPDATE with brand colour
    tertiary: "#84a59d",    // hover states — UPDATE with brand colour
    highlight: "rgba(143, 159, 169, 0.15)", // highlights
    textHighlight: "#fff23688",
  },
  // ... darkMode similarly
},

6.2 Configure Fonts

Update typography in quartz.config.ts. Any font available on Google Fonts works:

typography: {
  header: "Schibsted Grotesk",  // or your brand header font
  body: "Source Sans Pro",       // or your brand body font
  code: "IBM Plex Mono",
},

6.3 Update Footer

In quartz.layout.ts, update the footer links:

footer: Component.Footer({
  links: {
    "Calab.ai": "https://calab.ai",
    GitHub: "https://github.com/calab-ai/calab-handbook",
  },
}),

6.4 Add Favicon

Place a favicon file (e.g., favicon.ico, favicon.svg, or icon.png) in a content/static/ directory. Quartz’s Plugin.Favicon() emitter handles favicon generation. Alternatively, place it in quartz/static/.

6.5 Configure Page Title

Already set in Step 1: pageTitle: "Calab.ai Handbook". Adjust pageTitleSuffix for browser tab titles.

Deliverable

Branded, customised site matching Calab.ai visual identity.

Verification

  • Site title appears correctly in the page header and browser tab
  • Theme colours are consistent and match brand guidelines
  • Footer links are correct
  • Favicon displays in browser tab
  • Dark mode and light mode both look professional
  • Site is readable on mobile devices

Step 7 — Documentation & Team Workflow

Goal: Document the publishing workflow and onboard the team.

Tasks

7.1 Create Publishing Workflow Documentation

Create docs/PUBLISHING_WORKFLOW.md:

# Publishing Workflow
 
## How to Publish a Note
 
1. Open the note in Obsidian (or any text editor)
2. Add to frontmatter: `publish: true`
3. Save the note
4. Commit the change: `git add content/path/to/note.md && git commit -m "publish: note title"`
5. Push to a feature branch: `git push -u origin publish/note-title`
6. Open a Pull Request for review
7. Once approved and merged to `main`, the site auto-builds and deploys (2–4 minutes)
 
## How to Unpublish a Note
 
1. Remove `publish: true` from frontmatter (or set to `false`)
2. Commit and push the change
3. After merge to `main`, the note will be removed from the next build
 
## Quick Publish (for authorised direct pushers)
 
If you have push access to `main` and the change doesn't require review:
 
```bash
git add content/path/to/note.md
git commit -m "publish: note title"
git push origin main
```

Publishing Guidelines

  • Only publish notes marked publish: true
  • Review content for sensitive information before publishing
  • Ensure linked notes are also published (or links will show as dimmed/unresolved)
  • Do NOT add publish: true to files in metadata/ or 99 Archive/ directories
  • When in doubt, use a Pull Request

For Non-Technical Contributors

  1. Install GitHub Desktop
  2. Clone the repository
  3. Edit notes in Obsidian as usual
  4. Add publish: true to the Properties panel
  5. In GitHub Desktop: commit changes → push → create PR

Frontmatter Reference

KeyRequiredDescription
publish: trueYesMarks note for publishing
title: "Page Title"NoOverride the page title (defaults to filename)
description: "..."NoPage description for link previews
tags: [tag1, tag2]NoTags displayed on the page
aliases: [name1]NoAlternative names for wikilink resolution
date: YYYY-MM-DDNoPublication date
draft: trueNoExclude from publishing (alternative to removing publish: true)

Site URL

[Insert live site URL here]


**7.2 Create Repository Structure Documentation**

Create `docs/REPOSITORY_STRUCTURE.md`:

```markdown
# Repository Structure

## Single-Repository Model

| Directory | Purpose | Published to Site? |
|-----------|---------|-------------------|
| `content/` | Obsidian vault content | Yes (notes with `publish: true`) |
| `content/metadata/` | Internal taxonomy, templates, diagrams | No (ignored via `ignorePatterns`) |
| `content/99 Archive/` | Deprecated content | No (ignored) |
| `content/.obsidian/` | Obsidian app configuration | No (ignored) |
| `quartz/` | Quartz build engine (DO NOT edit manually) | N/A |
| `quartz.config.ts` | Site configuration | N/A |
| `quartz.layout.ts` | Site layout | N/A |
| `public/` | Build output (gitignored) | N/A |
| `docs/` | Plans, decisions, meta-docs | No (not in vault) |
| `.github/workflows/` | GitHub Actions CI/CD | N/A |

## How Publishing Works

1. Author marks note with `publish: true` in frontmatter
2. Author commits and pushes (or opens a PR)
3. On merge to `main`, GitHub Actions builds the site
4. GitHub Pages serves the result

## Content Governance

- All content changes go through git (commit → PR → review → merge)
- CODEOWNERS file controls who can approve changes to which sections
- Branch protection rules enforce PR reviews before merge
- GitHub Teams (synced with Entra ID) manage reviewer groups

7.3 Create Quartz Update Process Documentation

Create docs/QUARTZ_UPDATES.md:

# Updating Quartz
 
## When to Update
 
- Security patches from upstream
- New features needed
- Bug fixes
- Quarterly maintenance
 
## Update Process
 
1. Fetch the latest Quartz changes:
   ```bash
   git fetch upstream
   ```
  1. Create a feature branch for the update:

    git checkout -b chore/update-quartz
  2. Merge the upstream changes:

    git merge upstream/v4
  3. Resolve any merge conflicts (typically in quartz.config.ts or quartz.layout.ts — keep your customisations)

  4. Test locally:

    npm ci
    npx quartz build --serve
  5. If everything looks good, push and create a PR:

    git push -u origin chore/update-quartz
    gh pr create --title "chore: update Quartz to latest" --body "Updates Quartz build system to latest upstream version"
  6. Review the PR, verify the GitHub Actions build passes, and merge


#### Deliverable

Complete documentation for team adoption.

#### Verification

- All three docs exist in `docs/`
- Workflow documentation is accurate and actionable
- A team member can follow the publishing workflow independently

---

### Step 8 — Testing & Quality Assurance

**Goal:** Ensure site reliability, performance, and content quality.

#### Tasks

**8.1 Content Review**

- [ ] No sensitive or draft content accidentally published
- [ ] All published pages have accurate, current information
- [ ] All internal links resolve (no 404s)
- [ ] Images and diagrams display correctly
- [ ] Code blocks render with syntax highlighting
- [ ] Callouts/admonitions render correctly
- [ ] Tables are formatted properly
- [ ] Wikilinks resolve to the correct pages
- [ ] Mermaid diagrams render

**8.2 Cross-Browser Testing**

Test the live site on:
- [ ] Chrome (Windows)
- [ ] Edge (Windows)
- [ ] Firefox
- [ ] Safari (if available)
- [ ] Mobile Chrome / Mobile Safari

For each browser verify:
- Site loads, navigation works, search functions, no console errors
- Graph view is interactive
- Dark mode toggle works
- SPA navigation (page transitions) works smoothly

**8.3 Performance Audit**

Run Lighthouse audit (Chrome DevTools → Lighthouse):
- Target: 90+ in Performance, Accessibility, Best Practices, SEO
- Quartz generates optimised static assets with SPA routing, so performance should be excellent
- Common optimisations if needed: compress large images, ensure alt text on diagrams

**8.4 Security Review**

Scan published content for:
- [ ] API keys or tokens
- [ ] Internal IP addresses
- [ ] Employee personal information
- [ ] Credentials or passwords
- [ ] Sensitive business strategy details

**8.5 Verify HTTPS**

- [ ] Site uses HTTPS (GitHub Pages provides this automatically)
- [ ] No mixed content warnings
- [ ] Custom domain SSL certificate valid (if configured)

#### Deliverable

Quality-assured, secure, performant site.

#### Verification

- All checklist items pass
- Lighthouse scores meet targets
- Security scan clean
- No broken links

---

## 4. Decision Points & Options

### Decision 1: Custom Domain vs. Project Site

| Option | Pros | Cons |
|--------|------|------|
| **Custom domain (e.g., `handbook.calab.ai`)** — Recommended | Clean URLs, no subpath issues, professional appearance, `baseUrl` is simple | Requires DNS configuration |
| **Org user site (`calab-ai.github.io`)** | Root path works, free | Occupies the org's single user-site slot |
| **Project site (`calab-ai.github.io/calab-handbook/`)** | Free, simple, no DNS changes | Must include repo name in `baseUrl`; some edge cases with relative URL resolution |

**Recommendation:** Use a custom domain. It's the cleanest option and avoids all path-related edge cases. Note that Quartz uses relative URLs wherever possible, so project site paths work better than with Digital Garden/Eleventy — but a custom domain is still the simplest approach.

### Decision 2: Excalidraw Diagram Publishing Strategy

| Option | Pros | Cons |
|--------|------|------|
| **Enable auto-export to SVG** — Recommended | Automated, reliable rendering, no special build logic | Must remember to commit SVG files alongside source |
| **Manual SVG export** | Full control over exported quality | Manual step, easy to forget |
| **Skip Excalidraw on published site** | No extra workflow | Loses visual documentation on the site |

**Recommendation:** Enable auto-export to SVG in Excalidraw plugin settings. This is a one-time configuration that makes all future diagrams automatically available for publishing.

### Decision 3: Home Page Location

| Option | Pros | Cons |
|--------|------|------|
| **Create `content/index.md`** — Recommended | Quartz convention, cleanest approach | New file to create |
| **Rename `content/README.md` to `content/index.md`** | Reuses existing file | `README.md` is conventional for GitHub repository browsing |

**Recommendation:** Create a new `content/index.md` for the site home page. Keep `content/README.md` as-is for GitHub repository browsing (it doesn't need `publish: true`).

### Decision 4: Publishing Governance Model

| Option | Pros | Cons |
|--------|------|------|
| **All changes via PR** — Recommended | Full review trail, CODEOWNERS enforced, audit-friendly | Slightly more friction for quick fixes |
| **Direct push to `main` for minor changes** | Fast for small updates | Bypasses review; harder to audit |
| **Feature branch per content batch** | Organised, reviewable | More branch management overhead |

**Recommendation:** All content changes via PR. This is the whole reason for switching to Quartz — leverage git-native governance. Exception: initial setup commits during plan execution.

---

## 5. Risk Assessment

| Risk | Impact | Likelihood | Mitigation |
|------|--------|------------|------------|
| Non-technical users struggle with git | Reduced contribution rate | Medium | Provide GitHub Desktop guide, publish script, and clear step-by-step docs (Step 7) |
| `baseUrl` misconfigured | Broken links and assets | Medium | Test locally with `npx quartz build --serve` before deploying; verify after first deploy |
| Quartz upstream breaking changes | Build fails | Low | Pin to specific Quartz version; test updates on feature branch before merging |
| Large vault causes slow builds | Delayed deployments | Low | Quartz is fast; GitHub Actions allows 2,000 min/month for private repos |
| Excalidraw SVG auto-export not committed | Diagrams missing on site | Medium | Document workflow; add pre-commit check or CI warning for orphaned Excalidraw embeds |
| Merge conflicts in `quartz.config.ts` during Quartz updates | Update friction | Low | Configuration is a single file; conflicts are easy to resolve manually |
| ~~PAT token expiry~~ | ~~Publishing stops~~ | ~~N/A~~ | **Eliminated** — Quartz uses no PAT token |
| ~~Two-repo confusion~~ | ~~Wrong edits~~ | ~~N/A~~ | **Eliminated** — single repository model |
| ~~Plugin bypasses governance~~ | ~~Unreviewed content published~~ | ~~N/A~~ | **Eliminated** — publishing = git push |

---

## 6. Success Criteria

Implementation is successful when:

- [ ] Quartz 4 installed and configured in the `calab-handbook` repository
- [ ] GitHub Actions workflow deploys the site on every push to `main`
- [ ] GitHub Pages serves the site at a stable URL (custom domain or project site)
- [ ] Home page (`content/index.md`) published and accessible as the site root
- [ ] Minimum 10 pilot pages published with correct formatting and working links
- [ ] Selective publishing works: only notes with `publish: true` appear on the site
- [ ] `ignorePatterns` excludes `metadata/` subdirectories and `99 Archive/` from the site (file tree, search, pages)
- [ ] Full-text search indexes and returns published content
- [ ] Graph view displays connections between published pages
- [ ] Backlinks section shows incoming links
- [ ] Table of contents renders on long pages
- [ ] Excalidraw diagrams render via SVG export
- [ ] Dark mode and light mode both work correctly
- [ ] Site is responsive on mobile devices
- [ ] Publishing workflow documented and team can independently contribute
- [ ] Security review confirms no sensitive data exposed
- [ ] Cross-browser testing passes on Chrome, Edge, Firefox, and mobile

---

## 7. Appendices

### Appendix A: Plugin Compatibility Matrix

Current installed Obsidian plugins and their interaction with Quartz publishing:

| Plugin | Quartz Compatibility | Notes |
|--------|---------------------|-------|
| `obsidian-excalidraw-plugin` | ⚠️ Via SVG export | No native Excalidraw rendering; use auto-export to SVG |
| `drawio-obsidian` | ✅ Via SVG | Draw.io saves as SVG; embed SVG in notes |
| `templater-obsidian` | ✅ Local only | Templates are processed locally, won't affect published output |
| `omnisearch` | ✅ No interaction | Local search plugin, independent of Quartz's built-in site search |
| `tag-wrangler` | ✅ No interaction | Local tag management; Quartz parses tags from frontmatter |
| `obsidian-icon-folder` | ⚠️ Visual only | Folder icons won't appear on published site |
| `file-explorer-plus` | ✅ No interaction | Local file explorer enhancement |
| `file-explorer-note-count` | ✅ No interaction | Local UI enhancement |
| `obsidian-file-color` | ⚠️ Visual only | File colours won't appear on published site |
| `cm-editor-syntax-highlight-obsidian` | ✅ No interaction | Editor-only enhancement; Quartz has its own syntax highlighting |
| `colored-tags` | ⚠️ Partial | Tag colours may not transfer to site |
| `code-styler` | ⚠️ Partial | Some code styling may not render on site |
| `ninja-cursor` | ✅ No interaction | Editor-only |
| `cmdr` | ✅ No interaction | Command palette customisation |
| `vscode-editor` | ✅ No interaction | Editor alternative |
| `terminal` | ✅ No interaction | Local terminal |

**Not installed but referenced in previous plan versions:**
- `dataview` — NOT installed. Quartz does NOT support Dataview queries. For dynamic content visualisation, consider Mermaid diagrams (natively supported), static tables, or custom Quartz components in future.

### Appendix B: Frontmatter Quick Reference

```yaml
---
# Required for publishing
publish: true                 # Makes note publishable (ExplicitPublish filter)

# Optional metadata
title: "Page Title"           # Override page title (defaults to filename)
description: "Description"    # Page description for link previews and SEO
tags:                         # Tags displayed on page and in tag listings
  - governance
  - process
aliases:                      # Alternative names for wikilink resolution
  - "Alt Name"
date: 2026-02-13             # Publication date
draft: true                   # Alternative way to exclude from publishing

# URL override
permalink: "custom/url/path"  # Permanent URL regardless of file location

# Custom metadata (for your governance use)
page-status: draft
created: 2026-02-13
owner: guild-executive
type: index
---

Appendix C: Quartz Build Reference

Build command: npx quartz build Local preview: npx quartz build --serve Output directory: public/ Node.js version: 22.x Approximate build time: 30 seconds – 2 minutes (depends on content volume) Sync command: npx quartz sync (commits + pushes in one step)

Appendix D: Cost Estimate

ItemCost
GitHub repository (private)Free
GitHub Pages hostingFree
GitHub Actions (2,000 min/month for private repos)Free
Quartz 4Free (MIT license)
Custom domain (optional)~$12/year
Total$0–12/year

Appendix E: Comparison with Previous Plan (v1.0 — Digital Garden)

Aspectv1.0 (Digital Garden)v2.0 (Quartz 4)
Repository modelTwo repositoriesSingle repository
Publishing methodObsidian plugin (click to publish)Git push / PR merge
PR governanceBypassed (plugin pushes directly)Fully supported
CODEOWNERS supportNo (different repo)Yes (same repo)
PAT token requiredYes (per contributor)No
ExcalidrawNative renderingSVG export required
DataviewSupported (static render)Not supported
Theme fidelityExact Obsidian themeConfigurable (not identical)
Build systemEleventy (Nunjucks)Quartz (TypeScript/JSX)
Obsidian plugin neededYes (Digital Garden)No
Content directorysrc/site/notes/ (in template repo)content/ (in same repo)

Appendix F: Future Considerations (Out of Scope)

The following are explicitly out of scope for this plan and should be addressed in separate plans/decisions:

  1. AI Chat Assistant — RAG-based Q&A over published content (separate future project)
  2. Role Management UI — Web-based role editing interface
  3. Authentication/Access Control — If internal-only access is needed in future, a separate decision should evaluate Azure Static Web Apps or VPN-based access (per Decision 02 consequences)
  4. CODEOWNERS & GitHub Teams — Deferred to separate phase (per Plan 01 and docs/CODEOWNERS_DEFERRED.md)
  5. Dataview-equivalent functionality — If dynamic content queries are needed, evaluate custom Quartz components or Mermaid-based visualisations
  6. Custom Quartz Components — JSX-based custom components for specialised rendering (e.g., interactive org charts, dashboards)

0 items under this folder.