Plan: Site UX Improvements — Sidebar, Sorting, and Auto-Populated Listings
Version: 1.0 Date: 2026-02-14 Status: Draft Related:
1. Executive Summary
This plan addresses four interrelated UX issues with the published Quartz 4 handbook site:
-
Right sidebar missing on folder/tag pages — Table of Contents, Graph View, and Backlinks are absent on folder and tag pages because
defaultListPageLayouthas an emptyright: []array inquartz.layout.ts. This creates an inconsistent experience where regular content pages have a full right sidebar but folder/tag pages show a blank column. -
Graph View placement — The user asked whether Graph View should move to the bottom-left sidebar. After research, the recommendation is to keep Graph in the right sidebar (Quartz default, Obsidian Publish convention, and UX best practice) and simply ensure it appears on all page types.
-
Explorer sorting — Folders always sort before files regardless of name. The user wants mixed alphanumeric sorting so
README.mdsorts before02 Misc/based on its prefix number, not its type. The root cause is the defaultsortFninExplorer.tsxwhich explicitly groups folders first. -
MECE violations / auto-populated listings — 22 content files manually duplicate child page information (names, descriptions, practice lists) that becomes stale when pages are added or changed. A custom Quartz transformer plugin will replace manual listings with auto-populated
page-listcode blocks that query frontmatter at build time.
Key Benefits:
- Consistent sidebar experience across all page types
- Intuitive Explorer ordering matching the
NN prefixconvention - Self-maintaining content listings that never go stale
- Reduced maintenance burden for content authors
Prerequisites:
- Quartz 4 build working (
npx quartz buildsucceeds) node_modulesinstalled from Windows terminal (NOT WSL)- Plan 05 fully implemented (confirmed: commit
ce497f5)
2. Architecture / Context Overview
Current State
| Issue | Current Behavior | Root Cause |
|---|---|---|
| Missing right sidebar | Folder/tag pages show empty right column | defaultListPageLayout.right = [] in quartz.layout.ts |
| Graph placement | Only on content pages, in right sidebar | Not included in list layout’s right array |
| Explorer sorting | Folders always before files | sortFn in Explorer.tsx lines 32-48 has isFolder checks |
| Manual listings | 22 files with hardcoded child page info | No auto-populated listing mechanism in Quartz |
Proposed State
| Issue | Proposed Behavior |
|---|---|
| Right sidebar | Graph, ToC, and Backlinks appear on ALL page types |
| Graph placement | Stays in right sidebar (Quartz/Obsidian convention) |
| Explorer sorting | Pure alphanumeric sort by displayName, mixed folders and files |
| Auto listings | page-list code blocks auto-generate child page listings at build time |
Key Tradeoffs
| Decision | Choice | Rationale |
|---|---|---|
| Graph placement | Keep in right sidebar | Matches Quartz default, Obsidian Publish convention, and collocates navigational context (Graph + ToC + Backlinks). Moving to left would crowd Explorer and break mobile hamburger menu. |
| Explorer sort change | Override sortFn in quartz.layout.ts | Non-invasive — doesn’t modify Quartz source code, only configuration. Can be reverted by removing the option. |
| Auto-listings approach | Custom transformer plugin | Build-time markdown processing; output is real wikilinks that participate in graph/backlinks. No runtime dependency. Alternative (build script) rejected due to git noise from mutated source files. |
3. Implementation Steps
Step 1: Add Right Sidebar Components to List Page Layout
Goal: Folder pages and tag pages show Graph, Table of Contents, and Backlinks in the right sidebar, matching content pages.
File: quartz.layout.ts
Change: Replace the empty right: [] array in defaultListPageLayout with the same components used in defaultContentPageLayout:
// components for pages that display lists of pages (e.g. tags or folders)
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: [
Component.Graph(),
Component.DesktopOnly(Component.TableOfContents()),
Component.Backlinks(),
],
};Component behavior notes:
Graph()renders unconditionally — will always show on folder/tag pages.TableOfContentsreturnsnulliffileData.tocis falsy. For folder pages that use the first-file-as-content approach (from Plan 05), the first file’s headings generate a valid ToC. For synthesized folder pages (no matching content), the ToC will gracefully return null (no error, just hidden).BacklinksreturnsnullwhenhideWhenEmpty: true(default) and no backlinks exist. This is safe for folder pages.
Verification:
- Run
npx quartz build— no build errors - Run
npx quartz build --serveand navigate to a folder page (e.g.,/02-Guilds/GL03-Delivery-Guild/) - Confirm Graph View appears in the right sidebar
- Confirm ToC appears if the folder page has headings (most will, since first-file content has headings)
- Confirm Backlinks section appears (may say “No backlinks found” for some pages)
- Navigate to a regular content page and confirm right sidebar is unchanged
Deliverable: Updated quartz.layout.ts with populated right sidebar for list layout
Step 2: Fix Explorer Sorting — Mixed Alphanumeric Sort
Goal: Explorer sorts all items (folders and files) by display name using locale-aware numeric comparison, without grouping folders before files.
File: quartz.layout.ts
Change: Pass a custom sortFn to both Component.Explorer() calls that removes the folder-first grouping:
In defaultContentPageLayout.left:
Component.Explorer({
sortFn: (a, b) =>
a.displayName.localeCompare(b.displayName, undefined, {
numeric: true,
sensitivity: "base",
}),
}),In defaultListPageLayout.left:
Component.Explorer({
sortFn: (a, b) =>
a.displayName.localeCompare(b.displayName, undefined, {
numeric: true,
sensitivity: "base",
}),
}),How it works:
- The
sortFnis serialized to a string via.toString()and embedded as adata-data-fnsattribute on the Explorer<div>(seeExplorer.tsxline 74) - Client-side JavaScript in
explorer.inline.tsdeserializes it and passes it totrie.sort()infileTrie.ts - The
localeComparewith{ numeric: true }ensuresREADMEsorts before01 ...regardless of whether one is a file and the other a folder
Why override in quartz.layout.ts instead of modifying Explorer.tsx:
- Non-invasive: doesn’t modify Quartz framework source code
- Explicit: the sort behavior is visible in the layout configuration
- Reversible: removing the option restores the default behavior
- Future-proof: won’t conflict with Quartz upgrades
Verification:
- Run
npx quartz build --serve - Open the Explorer sidebar
- Navigate to a folder that has both numbered files and numbered subfolders (e.g., a guild folder with
README.mdandPractices/) - Confirm items sort by display name numerically:
READMEappears before01 ..., regardless of file/folder type - Verify deeply nested folders also sort correctly
Deliverable: Updated quartz.layout.ts with custom sortFn on both Explorer instances
Step 3: Create Auto-Populated Page List Transformer Plugin
Goal: Create a Quartz transformer plugin that replaces page-list code blocks in markdown with auto-generated page listings at build time.
File to create: quartz/plugins/transformers/pageList.ts
Syntax for content authors:
```page-list
path: 02 Guilds/GL03 Delivery Guild/Practices
sort: title
```Supported directives:
path:(required) — Content folder path to list children from (relative tocontent/)sort:(optional, default:title) — Sort bytitle,created, orfilenamedepth:(optional, default:1) — How many levels deep to scan (1= direct children only)
Implementation:
import { QuartzTransformerPlugin } from "../types";
import { Root, Code } from "mdast";
import { visit } from "unist-util-visit";
import path from "path";
import fs from "fs";
import matter from "gray-matter";
interface PageListOptions {
contentDir: string;
}
const defaultOptions: PageListOptions = {
contentDir: "content",
};
export const PageList: QuartzTransformerPlugin<Partial<PageListOptions>> = (
userOpts,
) => {
const opts = { ...defaultOptions, ...userOpts };
return {
name: "PageList",
markdownPlugins() {
return [
() => {
return (tree: Root, file) => {
const nodesToReplace: { node: Code; index: number; parent: any }[] =
[];
visit(tree, "code", (node: Code, index, parent) => {
if (node.lang === "page-list" && index !== undefined && parent) {
nodesToReplace.push({ node, index, parent });
}
});
for (const { node, index, parent } of nodesToReplace) {
// Parse directives from code block content
const directives = parseDirectives(node.value);
if (!directives.path) continue;
// Resolve the target folder
const targetDir = path.join(
process.cwd(),
opts.contentDir,
directives.path,
);
if (!fs.existsSync(targetDir)) {
// Replace with a warning comment
parent.children.splice(index, 1, {
type: "html",
value: `<!-- page-list: directory not found: ${directives.path} -->`,
});
continue;
}
// Read child files and their frontmatter
const depth = directives.depth ?? 1;
const entries = scanDirectory(targetDir, opts.contentDir, depth);
// Sort entries
const sortBy = directives.sort ?? "title";
entries.sort((a, b) => {
if (sortBy === "created") {
return (a.created ?? "").localeCompare(b.created ?? "");
}
if (sortBy === "filename") {
return a.filename.localeCompare(b.filename, undefined, {
numeric: true,
sensitivity: "base",
});
}
// default: title
return a.title.localeCompare(b.title, undefined, {
numeric: true,
sensitivity: "base",
});
});
// Generate markdown list items as AST nodes
const listItems = entries.map((entry) => {
const wikilink = entry.slug;
const description = entry.description
? ` - ${entry.description}`
: "";
return {
type: "listItem" as const,
spread: false,
children: [
{
type: "paragraph" as const,
children: [
{
type: "wikiLink" as const,
data: {
alias: entry.title,
permalink: wikilink,
},
value: wikilink,
} as any,
...(description
? [{ type: "text" as const, value: description }]
: []),
],
},
],
};
});
if (listItems.length === 0) {
parent.children.splice(index, 1, {
type: "paragraph",
children: [
{
type: "emphasis",
children: [{ type: "text", value: "No pages found." }],
},
],
});
} else {
parent.children.splice(index, 1, {
type: "list",
ordered: false,
spread: false,
children: listItems,
});
}
}
};
},
];
},
};
};
interface PageEntry {
title: string;
filename: string;
slug: string;
description?: string;
created?: string;
}
function parseDirectives(content: string): Record<string, any> {
const directives: Record<string, any> = {};
for (const line of content.split("\n")) {
const match = line.match(/^(\w+):\s*(.+)$/);
if (match) {
const key = match[1].trim();
const value = match[2].trim();
if (key === "depth") {
directives[key] = parseInt(value, 10);
} else {
directives[key] = value;
}
}
}
return directives;
}
function scanDirectory(
dir: string,
contentDir: string,
depth: number,
currentDepth: number = 0,
): PageEntry[] {
const entries: PageEntry[] = [];
if (currentDepth >= depth) return entries;
let items: string[];
try {
items = fs.readdirSync(dir);
} catch {
return entries;
}
for (const item of items) {
const fullPath = path.join(dir, item);
const stat = fs.statSync(fullPath);
if (stat.isFile() && item.endsWith(".md")) {
try {
const content = fs.readFileSync(fullPath, "utf-8");
const { data: fm } = matter(content);
// Build a wikilink-compatible path
// Strip the content/ prefix and .md extension
const contentRoot = path.join(process.cwd(), contentDir);
const relativePath = path.relative(contentRoot, fullPath);
const slug = relativePath.replace(/\.md$/, "").replace(/\\/g, "/");
entries.push({
title: fm.title || item.replace(/\.md$/, ""),
filename: item,
slug,
description: fm.description,
created: fm.created,
});
} catch {
// Skip files that can't be parsed
}
} else if (
stat.isDirectory() &&
!item.startsWith("_") &&
!item.startsWith(".")
) {
// For folders, look for an overview file (README.md or index.md)
const overviewFile = findOverviewFile(fullPath);
if (overviewFile) {
try {
const content = fs.readFileSync(overviewFile, "utf-8");
const { data: fm } = matter(content);
const contentRoot = path.join(process.cwd(), contentDir);
const relativePath = path.relative(contentRoot, overviewFile);
const slug = relativePath.replace(/\.md$/, "").replace(/\\/g, "/");
entries.push({
title: fm.title || item,
filename: item,
slug,
description: fm.description,
created: fm.created,
});
} catch {
// Skip folders that can't be parsed
}
}
// Recurse if depth allows
if (depth > 1) {
entries.push(
...scanDirectory(fullPath, contentDir, depth, currentDepth + 1),
);
}
}
}
return entries;
}
function findOverviewFile(dir: string): string | null {
try {
const items = fs
.readdirSync(dir)
.sort((a, b) =>
a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" }),
);
// Prefer README.md, then index.md, then first .md file
for (const item of items) {
if (item.toLowerCase() === "readme.md") return path.join(dir, item);
}
for (const item of items) {
if (item.toLowerCase() === "index.md") return path.join(dir, item);
}
for (const item of items) {
if (item.endsWith(".md")) return path.join(dir, item);
}
} catch {
// ignore
}
return null;
}Important implementation notes:
-
Why
gray-matterandfsinstead of Quartz’s content pipeline: Quartz transformer plugins process files individually viamarkdownPlugins(). Each file is processed independently, and transformers don’t have access toallFiles(that’s only available at the emitter/component level). Reading frontmatter from disk viagray-matteris the standard approach for transformers that need cross-file information. -
gray-matteris already a dependency of Quartz (used inquartz/plugins/transformers/frontmatter.ts), so no new install is needed. -
WikiLink AST nodes: The Obsidian Flavored Markdown transformer (
ofm.ts) processes wikilinks. SincePageListruns early in the transformer chain (we’ll register it before OFM), we can emit raw wikilink text. Alternatively, we emit HTML links directly. The implementation above uses awikiLinkAST node that OFM already handles. -
Alternative approach — emit raw markdown text: If the AST-based wikilink approach is fragile, a simpler approach is to replace the code block with raw markdown text that contains
[[wikilinks]], and let subsequent transformers parse it:
// Simpler alternative: emit raw markdown text
const markdownLines = entries.map((entry) => {
const desc = entry.description ? ` - ${entry.description}` : "";
return `- [[${entry.slug}|${entry.title}]]${desc}`;
});
parent.children.splice(index, 1, {
type: "html",
value: markdownLines.join("\n"),
});Decision: Start with the raw-markdown approach (simpler, more reliable), and only switch to AST manipulation if wikilinks don’t get processed by downstream transformers. The raw markdown must be parsed by OFM, which may or may not reparse HTML nodes. Test this during implementation and fall back to AST approach if needed.
Verification:
- Create a test markdown file with a
page-listcode block - Run
npx quartz build— no build errors - Inspect the generated HTML to confirm the listing was rendered
- Verify wikilinks in the listing are clickable and resolve correctly
- Verify the listing appears in the graph view (links are real, not just text)
Deliverable: New quartz/plugins/transformers/pageList.ts file
Step 4: Register the PageList Plugin
Goal: Add the PageList transformer to the Quartz configuration so it processes markdown files at build time.
File: quartz/plugins/transformers/index.ts
Change: Add export:
export { PageList } from "./pageList";File: quartz.config.ts
Change: Add Plugin.PageList() to the transformers array. It must come before Plugin.ObsidianFlavoredMarkdown() so that the generated wikilinks get processed by OFM:
transformers: [
Plugin.FrontMatter(),
Plugin.CreatedModifiedDate({
priority: ["frontmatter", "git", "filesystem"],
}),
Plugin.SyntaxHighlighting({
theme: {
light: "github-light",
dark: "github-dark-default",
},
keepBackground: false,
}),
Plugin.PageList(), // <-- Add here, before OFM
Plugin.ObsidianFlavoredMarkdown({ enableInHtmlEmbed: false }),
Plugin.GitHubFlavoredMarkdown(),
Plugin.TableOfContents(),
Plugin.CrawlLinks({ markdownLinkResolution: "shortest" }),
Plugin.Description(),
Plugin.Latex({ renderEngine: "katex" }),
],Verification:
- Run
npx quartz build— no build errors - The plugin logs no warnings about missing directories (if there are
page-listblocks referencing valid paths)
Deliverable: Updated quartz/plugins/transformers/index.ts and quartz.config.ts
Step 5: Convert MECE-Violating Files — SEVERE Priority
Goal: Replace manually-maintained child page listings in the three most severe MECE violators with page-list code blocks.
5a. Value Streams README (content/01 Value Streams/README.md)
Current: Lines 28-168 contain 7 manually-maintained value stream entries, each with hardcoded purpose, triggers, practices, and owner.
Proposed: Replace the “Active Value Streams” section with a page-list block, and move the detailed descriptions into each value stream’s own README.md (they already exist there from Plan 05). Keep the “What are Value Streams?”, “Governance”, and “Relationship” sections intact.
New “Active Value Streams” section:
## Active Value Streams
```page-list
path: 01 Value Streams
sort: filename
```
For detailed information about each Value Stream including purpose, boundaries, coordinated practices, and governance, click through to the individual Value Stream pages above.Content to remove: The 7 individual ### VS01... through ### VS07... subsections (lines 28-168), since all that information already exists in each value stream’s README.md.
Sections to keep: “Purpose”, “What are Value Streams?”, “Value Stream Governance”, “Relationship to Guilds and Practices”, “Related Resources”.
5b. Guilds README (content/02 Guilds/README.md)
Current: Lines 26-109 contain 5 manually-maintained guild entries with hardcoded focus, lead, practices, and purpose.
Proposed: Replace the “Active Guilds” section with a page-list block.
## Active Guilds
```page-list
path: 02 Guilds
sort: filename
```
For detailed information about each Guild including practices, governance, and review cadence, click through to the individual Guild pages above.Content to remove: The 5 individual ### GL01... through ### GL05... subsections (lines 26-109).
Sections to keep: “Purpose”, “What are Guilds?”, “Guild Governance”, “Related Resources”.
5c. Org Structure & Roles (content/00 Governance/04 Org Structure & Roles.md)
Current: Lines 61-91 list all 17 practices grouped by guild with hardcoded descriptions. Lines 119-127 list all 7 value streams in a table.
Proposed approach: This file is a reference document that provides a consolidated organizational overview. Rather than auto-populating everything, we should:
- Replace the “Guild Structure” table (lines 27-33) with a
page-listreferencing guilds - Replace the “Initial Practices” subsections (lines 61-91) with per-guild
page-listblocks - Replace the “Value Stream Structure” table (lines 119-127) with a
page-listreferencing value streams - Keep the prose sections (responsibilities, leadership, governance model) intact
New “Initial Practices” section:
### Practices by Guild
Practices are organized within their parent Guilds. See each guild's Practices folder for the complete listing:
#### GL01 Executive Guild
```page-list
path: 02 Guilds/GL01 Executive Guild/Practices
sort: filename
```
#### GL02 Sales Guild
```page-list
path: 02 Guilds/GL02 Sales Guild/Practices
sort: filename
```
#### GL03 Delivery Guild
```page-list
path: 02 Guilds/GL03 Delivery Guild/Practices
sort: filename
```
#### GL04 Technology Guild
```page-list
path: 02 Guilds/GL04 Technology Guild/Practices
sort: filename
```
#### GL05 Administration Guild
```page-list
path: 02 Guilds/GL05 Administration Guild/Practices
sort: filename
```Verification:
- Run
npx quartz build— no build errors - Inspect generated HTML for each modified file
- Confirm auto-generated listings link to correct pages
- Confirm no information was lost (all detail lives in the linked overview pages)
Deliverable: 3 content files updated with page-list blocks
Step 6: Convert MECE-Violating Files — MODERATE Priority
Goal: Replace manually-maintained practice listings in guild overviews and practice overviews.
6a. Guild Overview Files (5 files)
Files:
content/02 Guilds/GL01 Executive Guild/README.mdcontent/02 Guilds/GL02 Sales Guild/README.mdcontent/02 Guilds/GL03 Delivery Guild/README.mdcontent/02 Guilds/GL04 Technology Guild/README.mdcontent/02 Guilds/GL05 Administration Guild/README.md
Current pattern (e.g., GL03):
## Practices Under This Guild
- [[Business Analysis Management]] - Requirements elicitation, analysis...
- [[Solution Eng Management]] - Solution architecture...
- [[Project Mgmt]] - Project planning...
- [[Product Mgmt]] - Product strategy...Replace with:
## Practices Under This Guild
```page-list
path: 02 Guilds/GL03 Delivery Guild/Practices
sort: filename
```Adjust the path: for each guild.
6b. Practice Overview Files (5 files)
Files:
content/02 Guilds/GL01 Executive Guild/Practices/README.mdcontent/02 Guilds/GL02 Sales Guild/Practices/README.mdcontent/02 Guilds/GL03 Delivery Guild/Practices/README.mdcontent/02 Guilds/GL04 Technology Guild/Practices/README.mdcontent/02 Guilds/GL05 Administration Guild/Practices/README.md
Current pattern (e.g., GL03 Practices):
## Practices
- [[.../Business Analysis Management/README|Business Analysis Management]] - ...
- [[.../Solution Eng Management/README|Solution Eng Management]] - ...Replace with:
## Practices
```page-list
path: 02 Guilds/GL03 Delivery Guild/Practices
sort: filename
```6c. Company Products Overview
File: content/03 Products/README.md
Replace any manually-maintained product listing with:
```page-list
path: 03 Products
sort: filename
```Verification:
- Run
npx quartz build— no build errors - Spot-check 2-3 generated pages to confirm listings appear correctly
- Add a new practice folder with a
README.mdand rebuild — confirm it automatically appears in the listing without editing any other file
Deliverable: 11 content files updated with page-list blocks
Step 7: Handle Value Stream Practice Cross-References (LOW Priority)
Goal: Evaluate the 7 value stream README.md files that list coordinated practices.
Files:
content/01 Value Streams/VS01-VS07 .../README.md
Assessment: These files list practices as cross-references (e.g., “This Value Stream coordinates: Pipeline Mgmt, Proposal Mgmt…”). These are intentional cross-references, not MECE violations — a value stream by design references practices from multiple guilds.
Recommendation: Do NOT auto-populate these. The practice list for a value stream is a curated, intentional mapping that requires human judgment. Auto-populating from a folder path would not work because practices coordinated by a value stream come from different guild folders.
Action: Leave these files as-is. Add a comment in each file noting that the practice list is intentionally curated:
<!-- Coordinated practices are intentionally curated for this value stream. Do not auto-populate. -->Deliverable: 7 value stream overview files with clarifying comments (optional)
Step 8: Update Knowledge Standards
Goal: Document the page-list convention in the Knowledge Standards.
File: content/00 Governance/05 Knowledge Standards.md
Change: Add a new section under “Core Principles” or “Structural Standards”:
### Auto-Populated Listings
Use `page-list` code blocks to generate automatic child page listings instead of manually maintaining lists:
```page-list
path: 02 Guilds/GL03 Delivery Guild/Practices
sort: filename
```
**Directives:**
- `path:` (required) — Folder path relative to `content/`
- `sort:` (optional) — Sort by `title`, `created`, or `filename` (default: `title`)
- `depth:` (optional) — Scan depth, `1` = direct children only (default: `1`)
**When to use:**
- Index pages listing child pages (guilds, practices, value streams)
- Overview pages listing sibling practices
- Any listing that should automatically update when pages are added/removed
**When NOT to use:**
- Curated cross-references (e.g., a value stream listing its coordinated practices from different guilds)
- Static reference tables
- Listings that require custom descriptions not found in frontmatterAlso update the SSOT principle to reference auto-populated listings:
- **Auto-populate listings:** Use `page-list` code blocks instead of manually maintaining child page lists. See [Auto-Populated Listings](#auto-populated-listings).Verification:
- Knowledge Standards page builds correctly
- The new section renders as expected
Deliverable: Updated Knowledge Standards document
Step 9: Build, Verify, and Test
Goal: Validate the complete implementation end-to-end.
Commands:
# Build the site
npx quartz build
# Start dev server for manual testing
npx quartz build --serveManual verification checklist:
Right Sidebar (Step 1):
- Folder pages show Graph View in right sidebar
- Folder pages show ToC when content has headings
- Folder pages show Backlinks section
- Tag pages show same right sidebar components
- Regular content pages are unchanged
Explorer Sorting (Step 2):
-
READMEsorts before any01 ...folder/file - Mixed files and folders sort by display name, not by type
- Deeply nested folders sort correctly
- Mobile Explorer (hamburger menu) sorts correctly
Auto-Populated Listings (Steps 3-6):
- Value Streams README shows auto-generated list of value streams
- Guilds README shows auto-generated list of guilds
- Each guild overview shows auto-generated practice list
- Each practice overview shows auto-generated practice list
- Links in auto-generated listings are clickable and navigate correctly
- Links appear in Graph View (are real wikilinks, not plain text)
- Adding a new folder with
README.mdand rebuilding adds it to the listing automatically
No regressions:
- Site builds without errors or warnings
- All
publish: truepages render correctly - Breadcrumbs work on all page types
- Search works correctly
- SPA navigation works correctly
Deliverable: Fully verified working site
4. Decision Points & Options
Decision 1: Graph View Placement
Context: User asked about moving Graph View to bottom-left sidebar.
Research findings:
- Quartz 4 default: Graph in right sidebar (confirmed from default
quartz.layout.tsand the Quartz docs site itself) - Obsidian Publish: Graph in right sidebar
- UX principle: Right sidebar = contextual navigation aids (ToC, Graph, Backlinks). Left sidebar = structural navigation (Explorer, Search).
- Mobile concern: Left sidebar becomes the hamburger menu on mobile. Adding Graph to left would increase mobile menu clutter.
Options:
| Option | Pros | Cons |
|---|---|---|
| A) Keep Graph in right sidebar (Recommended) | Matches conventions; clean mobile UX; collocates contextual navigation | No change for users who expected a move |
| B) Move to bottom-left sidebar | User’s initial preference | Breaks convention; clutters mobile menu; separates Graph from related contextual components |
| C) Show in both (duplicate) | Maximum visibility | Duplicate rendering; performance cost; confusing |
Recommendation: Option A — Keep Graph in right sidebar. The fix for the user’s original concern (Graph missing on folder pages) is accomplished by adding it to defaultListPageLayout.right, not by moving it.
Decision 2: Auto-Listing Approach
Context: Need auto-populated page listings. Multiple approaches available.
Options:
| Option | Pros | Cons |
|---|---|---|
| A) Custom transformer plugin (Recommended) | Build-time; real wikilinks; content stays clean; no git noise | Requires new plugin code; disk I/O for frontmatter |
| B) Build-time script that mutates source files | Simple concept | Creates git noise; modifies source files; breaks SSOT |
| C) Custom emitter plugin | Full access to allFiles | Overkill; only works for dedicated listing pages, not inline sections |
| D) Client-side JavaScript | No build changes | No SSR; not in graph; poor SEO; fragile |
Recommendation: Option A — Custom transformer plugin. It runs at build time, produces real links, and doesn’t modify source files.
Decision 3: What to Do About 03 Operational Governance.md Legacy Practice List
Context: content/00 Governance/03 Operational Governance.md contains ~30 legacy management practice definitions (lines 322-420) that predate the guild-practice structure. The file already has a note saying practices have been implemented in the Guild-Practice structure.
Options:
| Option | Pros | Cons |
|---|---|---|
| A) Leave as-is (Recommended) | Historical reference document; already has disclaimer note | Minor duplication, but clearly marked as legacy |
| B) Replace with page-list blocks | Reduces duplication | Loses the original practice taxonomy which differs from current |
C) Archive to _99 Archive/ | Cleanest removal | Loses access to foundational document; may have value as context |
Recommendation: Option A — Leave as-is. The file serves as a foundational reference and already has a clear disclaimer directing readers to the current guild-practice structure. The practice names and descriptions in this document intentionally differ from the current implementation and serve as historical context.
5. Risk Assessment
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
page-list wikilinks not processed by OFM | Medium | High | Test raw markdown approach first; fall back to AST manipulation or HTML links if needed |
gray-matter reads slow on large content dir | Low | Medium | Only reads files in target path:, not entire content tree |
| Explorer sort breaks mobile hamburger | Low | Medium | sortFn applies identically to desktop and mobile; test both |
| ToC returns null on folder pages with no headings | Expected | None | Graceful — component returns null, no error, ToC section just hidden |
| New plugin breaks build | Low | High | Plugin has try-catch blocks; test with npx quartz build after each step |
Content authors unfamiliar with page-list syntax | Medium | Low | Document in Knowledge Standards (Step 8); provide examples |
| Auto-populated listings show unpublished pages | Medium | Medium | The plugin lists files from disk, but ExplicitPublish filter removes unpublished pages from the build. Links to unpublished pages will render as plain text (Quartz default behavior for unresolved links). Consider adding publish frontmatter check to the plugin. |
Mitigation for unpublished page links: In Step 3, add an optional publishedOnly: true directive or check fm.publish === true when scanning. For now, since all content is in draft, this is not blocking. Implement if it becomes an issue.
6. Success Criteria
- Right sidebar visible on folder pages AND tag pages with Graph, ToC (when applicable), and Backlinks
- Explorer sorts files and folders by display name alphanumerically, without folders-first grouping
- Auto-populated listings work correctly in at least 14 files (3 severe + 11 moderate)
- Zero manual practice/guild/value-stream listings remain in index files
- New pages auto-appear in listings when added to the correct folder and site is rebuilt
- Site builds cleanly with
npx quartz build— no errors - All existing
publish: truepages render correctly without regressions - Knowledge Standards document the
page-listconvention - Graph View stays in right sidebar on all page types
7. Appendices
Appendix A: Complete MECE Violation Inventory
SEVERE (full detail duplication — Step 5):
| File | Duplicated Items | Lines |
|---|---|---|
content/01 Value Streams/README.md | 7 VS entries with purpose, triggers, practices | 28-168 |
content/02 Guilds/README.md | 5 guild entries with focus, lead, practices | 26-109 |
content/00 Governance/04 Org Structure & Roles.md | 5 guilds + 17 practices + 7 VSs | 27-33, 61-91, 119-127 |
MODERATE (practice listings — Step 6):
| File | Duplicated Items |
|---|---|
content/02 Guilds/GL01 Executive Guild/README.md | 3 practices |
content/02 Guilds/GL02 Sales Guild/README.md | 2 practices |
content/02 Guilds/GL03 Delivery Guild/README.md | 4 practices |
content/02 Guilds/GL04 Technology Guild/README.md | 3 practices |
content/02 Guilds/GL05 Administration Guild/README.md | 5 practices |
content/02 Guilds/GL01 Executive Guild/Practices/README.md | 3 practices |
content/02 Guilds/GL02 Sales Guild/Practices/README.md | 2 practices |
content/02 Guilds/GL03 Delivery Guild/Practices/README.md | 4 practices |
content/02 Guilds/GL04 Technology Guild/Practices/README.md | 3 practices |
content/02 Guilds/GL05 Administration Guild/Practices/README.md | 5 practices |
content/03 Products/README.md | 2 products |
LOW (intentional cross-references — Step 7, no change):
| File | Cross-References |
|---|---|
content/01 Value Streams/VS01 Lead to Cash/README.md | 6 coordinated practices |
content/01 Value Streams/VS03 Request to Release/README.md | 6 coordinated practices |
content/01 Value Streams/VS04 Incident to Resolution/README.md | 4 coordinated practices |
content/01 Value Streams/VS05 Hire to High Performance/README.md | 5 coordinated practices |
content/01 Value Streams/VS07 Strategy to Execution/README.md | 6 coordinated practices |
LEGACY (intentionally left as-is — Decision 3):
| File | Content |
|---|---|
content/00 Governance/03 Operational Governance.md | ~30 legacy practice definitions |
Appendix B: Key Quartz Source Files
| File | Role | Modified? |
|---|---|---|
quartz.layout.ts | Page layout config | Yes (Steps 1, 2) |
quartz.config.ts | Plugin registration | Yes (Step 4) |
quartz/plugins/transformers/index.ts | Transformer exports | Yes (Step 4) |
quartz/plugins/transformers/pageList.ts | Auto-populated listings | Created (Step 3) |
quartz/components/Explorer.tsx | Default sort function | No (overridden via config) |
quartz/components/Graph.tsx | Graph View component | No |
quartz/components/TableOfContents.tsx | ToC component | No |
quartz/components/Backlinks.tsx | Backlinks component | No |
quartz/plugins/emitters/folderPage.tsx | Folder page generation | No (modified in Plan 05) |
quartz/components/pages/FolderContent.tsx | Folder content + listing | No |
Appendix C: Commit Strategy
Suggested commits (one per logical change):
fix: add right sidebar components to folder and tag page layoutsfix: use alphanumeric sort for Explorer instead of folders-firstfeat: add page-list transformer plugin for auto-populated listingsrefactor: replace manual page listings with auto-populated page-list blocksdocs: document page-list convention in Knowledge Standards
Appendix D: Key Decisions Summary
| Decision | Choice | Rationale |
|---|---|---|
| Graph View placement | Keep in right sidebar | Quartz/Obsidian convention; clean mobile UX |
| Explorer sort | Override sortFn in config | Non-invasive; doesn’t modify framework source |
| Auto-listing mechanism | Custom transformer plugin | Build-time; real wikilinks; no source mutation |
| Legacy governance doc | Leave as-is | Historical reference; already has disclaimer |
| VS practice cross-refs | Leave as curated manual lists | Intentional cross-references spanning multiple guilds |