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.relativePath is available in Quartz’s component props at build time (confirmed — set in quartz/processors/parse.ts line 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

AspectCurrent Behaviour
Content editingManual: contributor must know the repository structure, find the correct file, create a branch, edit, and submit a PR
Page metadataEach page has fileData.relativePath (e.g., 00 Governance/01 How to Contribute.md) available at build time
Contribution guidance01 How to Contribute.md describes the full git workflow — assumes technical proficiency
Layout beforeBodyRenders Breadcrumbs, ArticleTitle, ContentMeta, TagList

Proposed State

AspectProposed Behaviour
Content editingOne-click: “Edit this page” link on every page opens GitHub’s file editor for that exact source file
Page metadatarelativePath is used to construct the edit URL at build time (static HTML)
Contribution guidanceUpdated to reference the edit button as the primary contribution path for content changes
Layout beforeBodyAdds 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

DecisionChoiceRationale
Edit URL targetGitHub’s native /edit/main/ URLHandles branch/fork creation automatically; no custom API needed; familiar UX
Component typeStatic <a> tag (no client-side JS)Edit URL is deterministic at build time; no need for runtime logic; SEO-friendly
Button visibilityAlways visible (no auth check)Static sites can’t do server-side auth cheaply; GitHub handles permissions; reducing friction maximises contributions
PlacementbeforeBody section after ContentMetaVisible near the top; contextually near the “last modified” date; matches common docs site convention
Component configurationConstructor options for repo owner/repo/branch/contentDirMakes 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 CaseHandling
Index/home pagerelativePath is "index.md" → valid GitHub URL → button appears
Folder names with spacesencodeURI() converts spaces to %20 → valid URL
Folder names with special chars (&, etc.)encodeURI() handles these correctly
Page without relativePathGuard clause if (!relativePath) return null → no button rendered
Tag pagesTag pages are auto-generated and don’t have source markdown files; relativePath may be undefined → guard clause prevents rendering
Folder pagesFolder 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:

  1. Home page (index.md) — button should appear, URL should point to content/index.md
  2. A nested page — button should appear with correctly encoded URL
  3. 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:

  1. Run npx quartz build — should complete without errors
  2. Run npx quartz build --serve — local preview at http://localhost:8080
  3. 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)
  4. Check mobile view — the edit button should be visible and tappable
  5. 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

OptionProsConsRecommendation
A. After ContentMeta, before TagListNear date/reading time; natural “meta” positionMay crowd the meta areaRecommended
B. After TagList (last in beforeBody)Clearly separated from content metadataFarther from article title; less visibleAcceptable alternative
C. After ArticleTitle, before ContentMetaMost prominent positionMay look odd above the dateNot 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

OptionProsConsRecommendation
A. main branchGitHub’s default PR target; simpleUsers edit against latest merged contentRecommended
B. develop branchCould target a staging branchAdds complexity; this repo doesn’t use a develop branchNot recommended

Recommendation: Option A. The repo uses main as its only long-lived branch (confirmed in deploy.yml).


5. Risk Assessment

RiskLikelihoodImpactMitigation
relativePath undefined for some page typesLowLowGuard clause in component returns null; tested against tag/folder pages
URL encoding issues with exotic characters in file namesVery LowLowencodeURI() handles standard Unicode; file naming conventions restrict special chars
Users editing wrong file (stale build vs. current main)LowLowGitHub’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 usersLowMediumRepo access is gated by GitHub permissions; PR review process provides oversight
Breaking Quartz upstream mergeLowMediumComponent 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)LowLowGuard clause on relativePath; auto-generated pages (tags, folder listings) typically lack relativePath

6. Success Criteria

Functional Criteria

  1. Every published content page displays an “Edit this page” link in the beforeBody section
  2. Clicking the link opens the correct GitHub file editor URL in a new tab
  3. The GitHub edit URL correctly encodes folder and file names with spaces and special characters
  4. Auto-generated pages (tag pages) do NOT display the edit button
  5. The component is configurable via constructor options (repoOwner, repoName, branch, contentDir)

Non-Functional Criteria

  1. The edit button renders correctly on both desktop and mobile viewports
  2. The edit button styling is consistent with the existing ContentMeta component (muted, professional)
  3. No breaking changes to the Quartz build (npx quartz build succeeds)
  4. No client-side JavaScript is required (pure static HTML <a> tag)
  5. 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)

FilePurpose
quartz/components/EditButton.tsxEdit button Quartz component
quartz/components/styles/editButton.scssStyles for the edit button
content/metadata/plans/09 Edit Content Button/README.mdThis plan

Modified Files (3)

FileChanges
quartz/components/index.tsImport and export EditButton
quartz.layout.tsAdd Component.EditButton() to beforeBody arrays
content/00 Governance/01 How to Contribute.mdAdd “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:

PagerelativePathEdit URL
How to Contribute00 Governance/01 How to Contribute.mdhttps://github.com/calab-ai/calab-handbook/edit/main/content/00%20Company%20Governance/01%20How%20to%20Contribute.md
Home pageindex.mdhttps://github.com/calab-ai/calab-handbook/edit/main/content/index.md
VS01 Overview01 Value Streams/VS01 Hire to High Performer/README.mdhttps://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 TypeWhat 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 inGitHub redirects to login page, then proceeds with the appropriate flow above.

0 items under this folder.