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:

  1. Right sidebar missing on folder/tag pages — Table of Contents, Graph View, and Backlinks are absent on folder and tag pages because defaultListPageLayout has an empty right: [] array in quartz.layout.ts. This creates an inconsistent experience where regular content pages have a full right sidebar but folder/tag pages show a blank column.

  2. 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.

  3. Explorer sorting — Folders always sort before files regardless of name. The user wants mixed alphanumeric sorting so README.md sorts before 02 Misc/ based on its prefix number, not its type. The root cause is the default sortFn in Explorer.tsx which explicitly groups folders first.

  4. 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-list code blocks that query frontmatter at build time.

Key Benefits:

  • Consistent sidebar experience across all page types
  • Intuitive Explorer ordering matching the NN prefix convention
  • Self-maintaining content listings that never go stale
  • Reduced maintenance burden for content authors

Prerequisites:

  • Quartz 4 build working (npx quartz build succeeds)
  • node_modules installed from Windows terminal (NOT WSL)
  • Plan 05 fully implemented (confirmed: commit ce497f5)

2. Architecture / Context Overview

Current State

IssueCurrent BehaviorRoot Cause
Missing right sidebarFolder/tag pages show empty right columndefaultListPageLayout.right = [] in quartz.layout.ts
Graph placementOnly on content pages, in right sidebarNot included in list layout’s right array
Explorer sortingFolders always before filessortFn in Explorer.tsx lines 32-48 has isFolder checks
Manual listings22 files with hardcoded child page infoNo auto-populated listing mechanism in Quartz

Proposed State

IssueProposed Behavior
Right sidebarGraph, ToC, and Backlinks appear on ALL page types
Graph placementStays in right sidebar (Quartz/Obsidian convention)
Explorer sortingPure alphanumeric sort by displayName, mixed folders and files
Auto listingspage-list code blocks auto-generate child page listings at build time

Key Tradeoffs

DecisionChoiceRationale
Graph placementKeep in right sidebarMatches 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 changeOverride sortFn in quartz.layout.tsNon-invasive — doesn’t modify Quartz source code, only configuration. Can be reverted by removing the option.
Auto-listings approachCustom transformer pluginBuild-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.
  • TableOfContents returns null if fileData.toc is 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).
  • Backlinks returns null when hideWhenEmpty: true (default) and no backlinks exist. This is safe for folder pages.

Verification:

  1. Run npx quartz build — no build errors
  2. Run npx quartz build --serve and navigate to a folder page (e.g., /02-Guilds/GL03-Delivery-Guild/)
  3. Confirm Graph View appears in the right sidebar
  4. Confirm ToC appears if the folder page has headings (most will, since first-file content has headings)
  5. Confirm Backlinks section appears (may say “No backlinks found” for some pages)
  6. 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 sortFn is serialized to a string via .toString() and embedded as a data-data-fns attribute on the Explorer <div> (see Explorer.tsx line 74)
  • Client-side JavaScript in explorer.inline.ts deserializes it and passes it to trie.sort() in fileTrie.ts
  • The localeCompare with { numeric: true } ensures README sorts before 01 ... 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:

  1. Run npx quartz build --serve
  2. Open the Explorer sidebar
  3. Navigate to a folder that has both numbered files and numbered subfolders (e.g., a guild folder with README.md and Practices/)
  4. Confirm items sort by display name numerically: README appears before 01 ..., regardless of file/folder type
  5. 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 to content/)
  • sort: (optional, default: title) — Sort by title, created, or filename
  • depth: (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:

  1. Why gray-matter and fs instead of Quartz’s content pipeline: Quartz transformer plugins process files individually via markdownPlugins(). Each file is processed independently, and transformers don’t have access to allFiles (that’s only available at the emitter/component level). Reading frontmatter from disk via gray-matter is the standard approach for transformers that need cross-file information.

  2. gray-matter is already a dependency of Quartz (used in quartz/plugins/transformers/frontmatter.ts), so no new install is needed.

  3. WikiLink AST nodes: The Obsidian Flavored Markdown transformer (ofm.ts) processes wikilinks. Since PageList runs 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 a wikiLink AST node that OFM already handles.

  4. 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:

  1. Create a test markdown file with a page-list code block
  2. Run npx quartz build — no build errors
  3. Inspect the generated HTML to confirm the listing was rendered
  4. Verify wikilinks in the listing are clickable and resolve correctly
  5. 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:

  1. Run npx quartz build — no build errors
  2. The plugin logs no warnings about missing directories (if there are page-list blocks 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:

  1. Replace the “Guild Structure” table (lines 27-33) with a page-list referencing guilds
  2. Replace the “Initial Practices” subsections (lines 61-91) with per-guild page-list blocks
  3. Replace the “Value Stream Structure” table (lines 119-127) with a page-list referencing value streams
  4. 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:

  1. Run npx quartz build — no build errors
  2. Inspect generated HTML for each modified file
  3. Confirm auto-generated listings link to correct pages
  4. 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.md
  • content/02 Guilds/GL02 Sales Guild/README.md
  • content/02 Guilds/GL03 Delivery Guild/README.md
  • content/02 Guilds/GL04 Technology Guild/README.md
  • content/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.md
  • content/02 Guilds/GL02 Sales Guild/Practices/README.md
  • content/02 Guilds/GL03 Delivery Guild/Practices/README.md
  • content/02 Guilds/GL04 Technology Guild/Practices/README.md
  • content/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:

  1. Run npx quartz build — no build errors
  2. Spot-check 2-3 generated pages to confirm listings appear correctly
  3. Add a new practice folder with a README.md and 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 frontmatter

Also 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 --serve

Manual 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):

  • README sorts before any 01 ... 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.md and rebuilding adds it to the listing automatically

No regressions:

  • Site builds without errors or warnings
  • All publish: true pages 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.ts and 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:

OptionProsCons
A) Keep Graph in right sidebar (Recommended)Matches conventions; clean mobile UX; collocates contextual navigationNo change for users who expected a move
B) Move to bottom-left sidebarUser’s initial preferenceBreaks convention; clutters mobile menu; separates Graph from related contextual components
C) Show in both (duplicate)Maximum visibilityDuplicate 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:

OptionProsCons
A) Custom transformer plugin (Recommended)Build-time; real wikilinks; content stays clean; no git noiseRequires new plugin code; disk I/O for frontmatter
B) Build-time script that mutates source filesSimple conceptCreates git noise; modifies source files; breaks SSOT
C) Custom emitter pluginFull access to allFilesOverkill; only works for dedicated listing pages, not inline sections
D) Client-side JavaScriptNo build changesNo 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:

OptionProsCons
A) Leave as-is (Recommended)Historical reference document; already has disclaimer noteMinor duplication, but clearly marked as legacy
B) Replace with page-list blocksReduces duplicationLoses the original practice taxonomy which differs from current
C) Archive to _99 Archive/Cleanest removalLoses 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

RiskLikelihoodImpactMitigation
page-list wikilinks not processed by OFMMediumHighTest raw markdown approach first; fall back to AST manipulation or HTML links if needed
gray-matter reads slow on large content dirLowMediumOnly reads files in target path:, not entire content tree
Explorer sort breaks mobile hamburgerLowMediumsortFn applies identically to desktop and mobile; test both
ToC returns null on folder pages with no headingsExpectedNoneGraceful — component returns null, no error, ToC section just hidden
New plugin breaks buildLowHighPlugin has try-catch blocks; test with npx quartz build after each step
Content authors unfamiliar with page-list syntaxMediumLowDocument in Knowledge Standards (Step 8); provide examples
Auto-populated listings show unpublished pagesMediumMediumThe 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

  1. Right sidebar visible on folder pages AND tag pages with Graph, ToC (when applicable), and Backlinks
  2. Explorer sorts files and folders by display name alphanumerically, without folders-first grouping
  3. Auto-populated listings work correctly in at least 14 files (3 severe + 11 moderate)
  4. Zero manual practice/guild/value-stream listings remain in index files
  5. New pages auto-appear in listings when added to the correct folder and site is rebuilt
  6. Site builds cleanly with npx quartz build — no errors
  7. All existing publish: true pages render correctly without regressions
  8. Knowledge Standards document the page-list convention
  9. Graph View stays in right sidebar on all page types

7. Appendices

Appendix A: Complete MECE Violation Inventory

SEVERE (full detail duplication — Step 5):

FileDuplicated ItemsLines
content/01 Value Streams/README.md7 VS entries with purpose, triggers, practices28-168
content/02 Guilds/README.md5 guild entries with focus, lead, practices26-109
content/00 Governance/04 Org Structure & Roles.md5 guilds + 17 practices + 7 VSs27-33, 61-91, 119-127

MODERATE (practice listings — Step 6):

FileDuplicated Items
content/02 Guilds/GL01 Executive Guild/README.md3 practices
content/02 Guilds/GL02 Sales Guild/README.md2 practices
content/02 Guilds/GL03 Delivery Guild/README.md4 practices
content/02 Guilds/GL04 Technology Guild/README.md3 practices
content/02 Guilds/GL05 Administration Guild/README.md5 practices
content/02 Guilds/GL01 Executive Guild/Practices/README.md3 practices
content/02 Guilds/GL02 Sales Guild/Practices/README.md2 practices
content/02 Guilds/GL03 Delivery Guild/Practices/README.md4 practices
content/02 Guilds/GL04 Technology Guild/Practices/README.md3 practices
content/02 Guilds/GL05 Administration Guild/Practices/README.md5 practices
content/03 Products/README.md2 products

LOW (intentional cross-references — Step 7, no change):

FileCross-References
content/01 Value Streams/VS01 Lead to Cash/README.md6 coordinated practices
content/01 Value Streams/VS03 Request to Release/README.md6 coordinated practices
content/01 Value Streams/VS04 Incident to Resolution/README.md4 coordinated practices
content/01 Value Streams/VS05 Hire to High Performance/README.md5 coordinated practices
content/01 Value Streams/VS07 Strategy to Execution/README.md6 coordinated practices

LEGACY (intentionally left as-is — Decision 3):

FileContent
content/00 Governance/03 Operational Governance.md~30 legacy practice definitions

Appendix B: Key Quartz Source Files

FileRoleModified?
quartz.layout.tsPage layout configYes (Steps 1, 2)
quartz.config.tsPlugin registrationYes (Step 4)
quartz/plugins/transformers/index.tsTransformer exportsYes (Step 4)
quartz/plugins/transformers/pageList.tsAuto-populated listingsCreated (Step 3)
quartz/components/Explorer.tsxDefault sort functionNo (overridden via config)
quartz/components/Graph.tsxGraph View componentNo
quartz/components/TableOfContents.tsxToC componentNo
quartz/components/Backlinks.tsxBacklinks componentNo
quartz/plugins/emitters/folderPage.tsxFolder page generationNo (modified in Plan 05)
quartz/components/pages/FolderContent.tsxFolder content + listingNo

Appendix C: Commit Strategy

Suggested commits (one per logical change):

  1. fix: add right sidebar components to folder and tag page layouts
  2. fix: use alphanumeric sort for Explorer instead of folders-first
  3. feat: add page-list transformer plugin for auto-populated listings
  4. refactor: replace manual page listings with auto-populated page-list blocks
  5. docs: document page-list convention in Knowledge Standards

Appendix D: Key Decisions Summary

DecisionChoiceRationale
Graph View placementKeep in right sidebarQuartz/Obsidian convention; clean mobile UX
Explorer sortOverride sortFn in configNon-invasive; doesn’t modify framework source
Auto-listing mechanismCustom transformer pluginBuild-time; real wikilinks; no source mutation
Legacy governance docLeave as-isHistorical reference; already has disclaimer
VS practice cross-refsLeave as curated manual listsIntentional cross-references spanning multiple guilds

0 items under this folder.