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: truefrontmatter — 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:
- Decision 01: Site Rendering Technology — Approved (Amended): Quartz 4
- Decision 02: Site Hosting Platform — Approved: GitHub Pages
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:
- Author adds
publish: trueto a note’s frontmatter in the vault - Author commits the change and pushes to a feature branch
- Author opens a Pull Request for review (governed by CODEOWNERS and branch protection)
- On PR merge to
main, GitHub Actions triggers:npm ci→npx quartz build→ deploypublic/to GitHub Pages via artifacts - 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
| Component | Technology | Version |
|---|---|---|
| Static Site Generator | Quartz 4 | v4.5.x |
| Runtime | Node.js | 22.x |
| Language | TypeScript / JSX | Latest |
| Styling | SCSS | Latest |
| Content Format | Obsidian-Flavored Markdown | — |
| Hosting | GitHub Pages | — |
| CI/CD | GitHub 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.
- Back up the
.obsidian/directory - Identify the current/active config files (no number suffix):
workspace.json,app.json,appearance.json,community-plugins.json,core-plugins.json - Delete all numbered duplicates (e.g.,
workspace 17.json,appearance 14.json, etc.) - Delete
.DS_Storefiles
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/**.zipStage 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.
- Delete the existing
gh-pagesbranch:git push origin --delete gh-pages - Delete the local branch:
git branch -D gh-pages - 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 statusshows.obsidian/config files are tracked (not ignored)gh-pagesbranch deleted (local and remote)digitalgardenno longer incommunity-plugins.json.gitignoreincludespublic/,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-temp1.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-temp1.3 Add Quartz Upstream Remote
This allows pulling future Quartz updates:
git remote add upstream https://github.com/jackyzha0/quartz.git1.4 Install Node Dependencies
npm ci1.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 withpublish: truein frontmatter are published. This replaces the defaultPlugin.RemoveDrafts().ignorePatterns— Completely excludesmetadata/subdirectories,99 Archive/,.obsidian/, and practice-levelmetadata/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 --serveOpen 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 buildcompletes without errorspublic/directory is generated with HTML outputpackage.jsonexists at repository root with Quartz dependenciesquartz.config.tshas Calab.ai-specific configurationquartz/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@v4Note: 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:
- Go to Settings → Pages
- Set Source to “GitHub Actions” (NOT “Deploy from a branch”)
- 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:
- Settings → Pages → Custom Domain → enter domain (e.g.,
handbook.calab.ai) - 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)
- For subdomain: Create CNAME record pointing to
- 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- Check GitHub Actions tab — the
deploy.ymlworkflow should trigger - Wait for build + deploy jobs to complete (2–4 minutes)
- Visit the GitHub Pages URL — should show a Quartz site with no content (since no notes have
publish: trueyet)
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.mdcontent/00 Governance/01 How to Contribute.mdcontent/00 Governance/02 Knowledge Standards.md
Value Streams (2 pages):
content/01 Value Streams/README.mdcontent/01 Value Streams/VS01 Lead to Cash/README.md
Guilds (3 pages):
content/02 Guilds/README.mdcontent/02 Guilds/GL04 Technology Guild/README.mdcontent/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 main3.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.tssyntax is valid (runnpx quartz buildlocally) - Ensure
content/index.mdexists and haspublish: true - Check that
ignorePatternsdon’t accidentally exclude content directories
If the site shows 404:
- Verify GitHub Pages source is set to “GitHub Actions”
- Delete any existing
github-pagesenvironment and let the workflow recreate it - Check
baseUrlmatches the actual deployment URL
If styles/assets are broken:
- Verify
baseUrlinquartz.config.tsis correct - If using a project site (not custom domain), ensure
baseUrlincludes 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:
- Settings → Excalidraw
- Under Export Settings, enable: Auto-export SVG
- Set export path to same folder as the
.excalidraw.mdfile (default) - This will automatically generate
.svgfiles alongside every.excalidraw.mdfile 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:
For existing notes that embed Excalidraw files, update to standard Markdown image syntax  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.mdsource files inmetadata/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
- Open an existing Excalidraw file in Obsidian (e.g.,
SG02 Organisational Reporting Structure.excalidraw.md) - Verify the auto-export creates a corresponding
.svgfile - Create a test note with
publish: truethat embeds the SVG - 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:
| Folder | Note Count | Priority | Contains Sensitive Data? | Status |
|---|---|---|---|---|
00 Governance/ | 6 | High | No | Not Started |
01 Value Streams/ (all VS) | ~50+ | High | No | Not Started |
02 Guilds/ (all GL) | ~30+ | High | No | Not Started |
03 Products/NeuralOps/ | 4+ | Medium | Review needed | Not Started |
Exclude from publishing (handled by ignorePatterns):
99 Archive/— deprecated contentmetadata/templater/— internal templatesmetadata/— 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
doneManual 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 Index02 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 mainOr, 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 --orderedOr 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: trueto files inmetadata/or99 Archive/directories - When in doubt, use a Pull Request
For Non-Technical Contributors
- Install GitHub Desktop
- Clone the repository
- Edit notes in Obsidian as usual
- Add
publish: trueto the Properties panel - In GitHub Desktop: commit changes → push → create PR
Frontmatter Reference
| Key | Required | Description |
|---|---|---|
publish: true | Yes | Marks note for publishing |
title: "Page Title" | No | Override the page title (defaults to filename) |
description: "..." | No | Page description for link previews |
tags: [tag1, tag2] | No | Tags displayed on the page |
aliases: [name1] | No | Alternative names for wikilink resolution |
date: YYYY-MM-DD | No | Publication date |
draft: true | No | Exclude 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
```-
Create a feature branch for the update:
git checkout -b chore/update-quartz -
Merge the upstream changes:
git merge upstream/v4 -
Resolve any merge conflicts (typically in
quartz.config.tsorquartz.layout.ts— keep your customisations) -
Test locally:
npm ci npx quartz build --serve -
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" -
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
| Item | Cost |
|---|---|
| GitHub repository (private) | Free |
| GitHub Pages hosting | Free |
| GitHub Actions (2,000 min/month for private repos) | Free |
| Quartz 4 | Free (MIT license) |
| Custom domain (optional) | ~$12/year |
| Total | $0–12/year |
Appendix E: Comparison with Previous Plan (v1.0 — Digital Garden)
| Aspect | v1.0 (Digital Garden) | v2.0 (Quartz 4) |
|---|---|---|
| Repository model | Two repositories | Single repository |
| Publishing method | Obsidian plugin (click to publish) | Git push / PR merge |
| PR governance | Bypassed (plugin pushes directly) | Fully supported |
| CODEOWNERS support | No (different repo) | Yes (same repo) |
| PAT token required | Yes (per contributor) | No |
| Excalidraw | Native rendering | SVG export required |
| Dataview | Supported (static render) | Not supported |
| Theme fidelity | Exact Obsidian theme | Configurable (not identical) |
| Build system | Eleventy (Nunjucks) | Quartz (TypeScript/JSX) |
| Obsidian plugin needed | Yes (Digital Garden) | No |
| Content directory | src/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:
- AI Chat Assistant — RAG-based Q&A over published content (separate future project)
- Role Management UI — Web-based role editing interface
- 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)
- CODEOWNERS & GitHub Teams — Deferred to separate phase (per Plan 01 and
docs/CODEOWNERS_DEFERRED.md) - Dataview-equivalent functionality — If dynamic content queries are needed, evaluate custom Quartz components or Mermaid-based visualisations
- Custom Quartz Components — JSX-based custom components for specialised rendering (e.g., interactive org charts, dashboards)