Plan: Edit Content Button
Version: 1.0 Date: 2026-02-23 Status: In Progress Related:
1. Executive Summary
This plan adds an “Edit this page” button to every published page on the Calab.ai Handbook site (handbook.calab.ai). The button links directly to GitHub’s native file editor for that page’s source markdown file. When a visitor clicks it, GitHub automatically handles branch creation and PR submission — no custom backend, API integration, or authentication layer is required.
Key Benefits:
- Zero-friction contribution — non-technical users can suggest changes to any page by clicking a single link and editing in the browser
- GitHub-native workflow — leverages GitHub’s built-in edit-file flow which auto-creates a fork (for external contributors) or a branch (for collaborators with write access), and opens a PR on save
- No authentication complexity — the button is visible to everyone; GitHub’s own permissions gate the actual editing capability
- Consistent with Plan 08 — complements the automated scaffolding workflow by providing an equally accessible path for editing existing content (whereas Plan 08 covers creating new structures)
Critical Dependencies:
- The repository is public (or the target audience has GitHub access to the repo)
- Quartz component system (TypeScript/Preact)
fileData.relativePathis available in Quartz’s component props at build time (confirmed — set inquartz/processors/parse.tsline 104)
Scope: A new Quartz component (EditButton) with associated styles, registered in the beforeBody section of the content page layout. The component generates a direct GitHub edit URL from the page’s source file path.
2. Architecture / Context Overview
Current State
| Aspect | Current Behaviour |
|---|---|
| Content editing | Manual: contributor must know the repository structure, find the correct file, create a branch, edit, and submit a PR |
| Page metadata | Each page has fileData.relativePath (e.g., 00 Governance/01 How to Contribute.md) available at build time |
| Contribution guidance | 01 How to Contribute.md describes the full git workflow — assumes technical proficiency |
Layout beforeBody | Renders Breadcrumbs, ArticleTitle, ContentMeta, TagList |
Proposed State
| Aspect | Proposed Behaviour |
|---|---|
| Content editing | One-click: “Edit this page” link on every page opens GitHub’s file editor for that exact source file |
| Page metadata | relativePath is used to construct the edit URL at build time (static HTML) |
| Contribution guidance | Updated to reference the edit button as the primary contribution path for content changes |
Layout beforeBody | Adds EditButton component after ContentMeta |
How It Works
┌─────────────────────────────┐
│ Quartz Build (npx quartz) │
│ │
│ For each content page: │
│ relativePath = "00 Co.." │
│ ↓ │
│ EditButton component │
│ Renders static <a> with │
│ href = GitHub edit URL │
└──────────┬──────────────────┘
│
▼
┌─────────────────────────────┐
│ Published Static HTML │
│ │
│ <a href="https://github. │
│ com/calab-ai/calab- │
│ handbook/edit/main/ │
│ content/00%20Company... │
│ /01%20How%20to...md"> │
│ Edit this page │
│ </a> │
└──────────┬──────────────────┘
│ User clicks
▼
┌─────────────────────────────┐
│ GitHub Web Editor │
│ │
│ • If collaborator: edits │
│ on a new branch │
│ • If external: forks repo │
│ then edits on fork │
│ • On save: PR created │
│ automatically │
└─────────────────────────────┘
Key Tradeoffs
| Decision | Choice | Rationale |
|---|---|---|
| Edit URL target | GitHub’s native /edit/main/ URL | Handles branch/fork creation automatically; no custom API needed; familiar UX |
| Component type | Static <a> tag (no client-side JS) | Edit URL is deterministic at build time; no need for runtime logic; SEO-friendly |
| Button visibility | Always visible (no auth check) | Static sites can’t do server-side auth cheaply; GitHub handles permissions; reducing friction maximises contributions |
| Placement | beforeBody section after ContentMeta | Visible near the top; contextually near the “last modified” date; matches common docs site convention |
| Component configuration | Constructor options for repo owner/repo/branch/contentDir | Makes the component reusable and configurable via quartz.layout.ts |
3. Implementation Steps
Step 1: Create the EditButton Component
Goal: A new Quartz component that renders a static “Edit this page” link pointing to the GitHub web editor for the current page’s source file.
File: quartz/components/EditButton.tsx
import {
QuartzComponent,
QuartzComponentConstructor,
QuartzComponentProps,
} from "./types";
import { classNames } from "../util/lang";
import style from "./styles/editButton.scss";
interface EditButtonOptions {
/** GitHub repository owner (org or user). Default: "calab-ai" */
repoOwner: string;
/** GitHub repository name. Default: "calab-handbook" */
repoName: string;
/** Branch to edit against. Default: "main" */
branch: string;
/** Content directory prefix (matches Quartz's content directory). Default: "content" */
contentDir: string;
}
const defaultOptions: EditButtonOptions = {
repoOwner: "calab-ai",
repoName: "calab-handbook",
branch: "main",
contentDir: "content",
};
export default ((userOpts?: Partial<EditButtonOptions>) => {
const opts: EditButtonOptions = { ...defaultOptions, ...userOpts };
const EditButton: QuartzComponent = ({
fileData,
displayClass,
}: QuartzComponentProps) => {
const relativePath = fileData.relativePath;
if (!relativePath) return null;
const repoFilePath = `${opts.contentDir}/${relativePath}`;
const editUrl = `https://github.com/${opts.repoOwner}/${opts.repoName}/edit/${opts.branch}/${encodeURI(repoFilePath)}`;
return (
<a
href={editUrl}
class={classNames(displayClass, "edit-button")}
target="_blank"
rel="noopener noreferrer"
title="Edit this page on GitHub"
>
<svg>...</svg>
<span>Edit this page</span>
</a>
);
};
EditButton.css = style;
return EditButton;
}) satisfies QuartzComponentConstructor;Verification: After creation, run npx quartz build and confirm no TypeScript compilation errors. Inspect the generated HTML for any content page and verify the <a class="edit-button"> element appears with the correct href.
Step 2: Create the EditButton Stylesheet
Goal: Style the edit button to be visually consistent with the existing ContentMeta component — understated, professional, and inline with the page metadata.
File: quartz/components/styles/editButton.scss
Verification: Build the site and visually confirm the edit button renders in a muted gray, transitions to the secondary colour on hover, and aligns neatly below the content meta area.
Step 3: Register the Component in the Component Index
Goal: Export EditButton from the Quartz component barrel file so it can be used in quartz.layout.ts.
File: quartz/components/index.ts
Verification: TypeScript compilation succeeds (npx quartz build); EditButton is available in the Component namespace.
Step 4: Add EditButton to the Page Layout
Goal: Place the EditButton in the beforeBody section of the content page layout, positioned after ContentMeta and before TagList.
File: quartz.layout.ts
Verification: Build the site (npx quartz build). Navigate to any content page in the built output and confirm the edit button appears between ContentMeta and TagList.
Step 5: Handle Edge Cases
Goal: Ensure the edit button handles special cases gracefully.
| Edge Case | Handling |
|---|---|
| Index/home page | relativePath is "index.md" → valid GitHub URL → button appears |
| Folder names with spaces | encodeURI() converts spaces to %20 → valid URL |
Folder names with special chars (&, etc.) | encodeURI() handles these correctly |
Page without relativePath | Guard clause if (!relativePath) return null → no button rendered |
| Tag pages | Tag pages are auto-generated and don’t have source markdown files; relativePath may be undefined → guard clause prevents rendering |
| Folder pages | Folder pages may have index.md files — if they have a relativePath, the edit button appears; if not, it doesn’t |
Verification: Spot-check the following pages in the built output:
- Home page (
index.md) — button should appear, URL should point tocontent/index.md - A nested page — button should appear with correctly encoded URL
- A tag page — button should NOT appear (no source file to edit)
Step 6: Update Contribution Documentation
Goal: Update the “How to Contribute” page to reference the new edit button as the primary path for content changes.
File: content/00 Governance/01 How to Contribute.md
Verification: Build the site and confirm the updated page renders correctly with the new section.
Step 7: Build and Smoke Test
Goal: Full build verification and manual smoke testing.
Steps:
- Run
npx quartz build— should complete without errors - Run
npx quartz build --serve— local preview athttp://localhost:8080 - Navigate to at least 5 different pages and verify:
- Edit button appears in the correct position (after ContentMeta, before TagList)
- Edit button is styled consistently (muted gray, hover effect)
- Clicking the edit button opens the correct GitHub file editor URL in a new tab
- The GitHub URL resolves to the correct file (verify the path matches)
- Check mobile view — the edit button should be visible and tappable
- Verify tag pages do NOT show the edit button
Verification: All 5 pages tested, all URLs correct, no visual regressions, mobile responsive.
4. Decision Points & Options
Decision 1: Placement Within beforeBody
| Option | Pros | Cons | Recommendation |
|---|---|---|---|
A. After ContentMeta, before TagList | Near date/reading time; natural “meta” position | May crowd the meta area | Recommended |
B. After TagList (last in beforeBody) | Clearly separated from content metadata | Farther from article title; less visible | Acceptable alternative |
C. After ArticleTitle, before ContentMeta | Most prominent position | May look odd above the date | Not recommended |
Recommendation: Option A. The edit button is conceptually part of page metadata (“when was this modified?” → “edit it yourself”), so grouping it near ContentMeta feels natural.
Decision 2: Edit URL Target Branch
| Option | Pros | Cons | Recommendation |
|---|---|---|---|
A. main branch | GitHub’s default PR target; simple | Users edit against latest merged content | Recommended |
B. develop branch | Could target a staging branch | Adds complexity; this repo doesn’t use a develop branch | Not recommended |
Recommendation: Option A. The repo uses main as its only long-lived branch (confirmed in deploy.yml).
5. Risk Assessment
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
relativePath undefined for some page types | Low | Low | Guard clause in component returns null; tested against tag/folder pages |
| URL encoding issues with exotic characters in file names | Very Low | Low | encodeURI() handles standard Unicode; file naming conventions restrict special chars |
Users editing wrong file (stale build vs. current main) | Low | Low | GitHub’s editor shows the current file on main; if the file was renamed/moved since the last build, GitHub shows a 404 — clear feedback |
| Spam PRs from external users | Low | Medium | Repo access is gated by GitHub permissions; PR review process provides oversight |
| Breaking Quartz upstream merge | Low | Medium | Component follows existing patterns (same structure as ReaderMode, Darkmode); isolated files with no upstream conflicts |
| Edit button on pages that shouldn’t be edited (e.g., auto-generated) | Low | Low | Guard clause on relativePath; auto-generated pages (tags, folder listings) typically lack relativePath |
6. Success Criteria
Functional Criteria
- Every published content page displays an “Edit this page” link in the
beforeBodysection - Clicking the link opens the correct GitHub file editor URL in a new tab
- The GitHub edit URL correctly encodes folder and file names with spaces and special characters
- Auto-generated pages (tag pages) do NOT display the edit button
- The component is configurable via constructor options (repoOwner, repoName, branch, contentDir)
Non-Functional Criteria
- The edit button renders correctly on both desktop and mobile viewports
- The edit button styling is consistent with the existing ContentMeta component (muted, professional)
- No breaking changes to the Quartz build (
npx quartz buildsucceeds) - No client-side JavaScript is required (pure static HTML
<a>tag) - Build time is not measurably affected (no API calls or async operations in the component)
7. Appendices
Appendix A: Complete File Inventory
New Files (3)
| File | Purpose |
|---|---|
quartz/components/EditButton.tsx | Edit button Quartz component |
quartz/components/styles/editButton.scss | Styles for the edit button |
content/metadata/plans/09 Edit Content Button/README.md | This plan |
Modified Files (3)
| File | Changes |
|---|---|
quartz/components/index.ts | Import and export EditButton |
quartz.layout.ts | Add Component.EditButton() to beforeBody arrays |
content/00 Governance/01 How to Contribute.md | Add “Quick Edit” section describing the edit button |
Appendix B: GitHub Edit URL Format
The GitHub web editor URL follows this pattern:
https://github.com/{owner}/{repo}/edit/{branch}/{path-to-file}
For this repository:
https://github.com/calab-ai/calab-handbook/edit/main/content/{relativePath}
Examples:
| Page | relativePath | Edit URL |
|---|---|---|
| How to Contribute | 00 Governance/01 How to Contribute.md | https://github.com/calab-ai/calab-handbook/edit/main/content/00%20Company%20Governance/01%20How%20to%20Contribute.md |
| Home page | index.md | https://github.com/calab-ai/calab-handbook/edit/main/content/index.md |
| VS01 Overview | 01 Value Streams/VS01 Hire to High Performer/README.md | https://github.com/calab-ai/calab-handbook/edit/main/content/01%20Value%20Streams/VS01%20Hire%20to%20High%20Performer/README.md |
Appendix C: GitHub Edit Flow for Different User Types
| User Type | What Happens on Click |
|---|---|
| Collaborator (write access) | GitHub opens the file editor. On save, creates a new branch and opens a PR against main. |
| Organisation member (read access) | GitHub prompts to fork the repo, then opens the file editor on their fork. On save, opens a cross-fork PR. |
| External user (no access) | If repo is public: same as org member (fork + PR). If private: GitHub shows a 404 or permission error. |
| Not logged in | GitHub redirects to login page, then proceeds with the appropriate flow above. |