# AGENTS.md - Coding Agent Instructions This document provides essential information for AI coding agents working in this repository. ## Project Overview **Quartz** is a static site generator for publishing digital gardens and notes as websites. Built with TypeScript, Preact, and unified/remark/rehype for markdown processing. | Stack | Technology | | ------------- | ----------------------------------------- | | Language | TypeScript 5.x (strict mode) | | Runtime | Node.js >=22 (v22.16.0 pinned) | | Package Mgr | npm >=10.9.2 | | Module System | ES Modules (`"type": "module"`) | | UI Framework | Preact 10.x (JSX with `react-jsx` pragma) | | Build Tool | esbuild | | Styling | SCSS via esbuild-sass-plugin | ## Build, Lint, and Test Commands ```bash # Type check and format check (CI validation) npm run check # Auto-format code with Prettier npm run format # Run all tests npm run test # Run a single test file npx tsx --test quartz/util/path.test.ts # Run tests matching a pattern (use --test-name-pattern) npx tsx --test --test-name-pattern="typeguards" quartz/util/path.test.ts # Build the static site npx quartz build # Build and serve with hot reload npx quartz build --serve # Profile build performance npm run profile ``` ### Test Files Location Tests use Node.js native test runner via `tsx`. Test files follow the `*.test.ts` pattern: - `quartz/util/path.test.ts` - `quartz/util/fileTrie.test.ts` - `quartz/components/scripts/search.test.ts` ## Code Style Guidelines ### Prettier Configuration (`.prettierrc`) ```json { "printWidth": 100, "tabWidth": 2, "semi": false, "trailingComma": "all", "quoteProps": "as-needed" } ``` **No ESLint** - only Prettier for formatting. Run `npm run format` before committing. ### TypeScript Configuration - **Strict mode enabled** (`strict: true`) - `noUnusedLocals: true` - no unused variables - `noUnusedParameters: true` - no unused function parameters - JSX configured for Preact (`jsxImportSource: "preact"`) ### Import Conventions ```typescript // 1. External packages first import { PluggableList } from "unified" import { visit } from "unist-util-visit" // 2. Internal utilities/types (relative paths) import { QuartzTransformerPlugin } from "../types" import { FilePath, slugifyFilePath } from "../../util/path" import { i18n } from "../../i18n" ``` ### Naming Conventions | Element | Convention | Example | | ---------------- | ------------ | ----------------------------------- | | Files (utils) | camelCase | `path.ts`, `fileTrie.ts` | | Files (comps) | PascalCase | `TableOfContents.tsx`, `Search.tsx` | | Types/Interfaces | PascalCase | `QuartzComponent`, `FullSlug` | | Type Guards | `is*` prefix | `isFilePath()`, `isFullSlug()` | | Constants | UPPER_CASE | `QUARTZ`, `UPSTREAM_NAME` | | Options types | `Options` | `interface Options { ... }` | ### Branded Types Pattern This codebase uses branded types for type-safe path handling: ```typescript type SlugLike = string & { __brand: T } export type FilePath = SlugLike<"filepath"> export type FullSlug = SlugLike<"full"> export type SimpleSlug = SlugLike<"simple"> // Always validate with type guards before using export function isFilePath(s: string): s is FilePath { ... } ``` ### Component Pattern (Preact) Components use a factory function pattern with attached static properties: ```typescript export default ((userOpts?: Partial) => { const opts: Options = { ...defaultOptions, ...userOpts } const ComponentName: QuartzComponent = ({ cfg, displayClass }: QuartzComponentProps) => { return
...
} ComponentName.css = style // SCSS styles ComponentName.afterDOMLoaded = script // Client-side JS return ComponentName }) satisfies QuartzComponentConstructor ``` ### Plugin Pattern Three plugin types: transformers, filters, and emitters. ```typescript export const PluginName: QuartzTransformerPlugin> = (userOpts) => { const opts = { ...defaultOptions, ...userOpts } return { name: "PluginName", markdownPlugins(ctx) { return [...] }, htmlPlugins(ctx) { return [...] }, externalResources(ctx) { return { js: [], css: [] } }, } } ``` ### Testing Pattern Use Node.js native test runner with `assert`: ```typescript import test, { describe, beforeEach } from "node:test" import assert from "node:assert" describe("FeatureName", () => { test("should do something", () => { assert.strictEqual(actual, expected) assert.deepStrictEqual(actualObj, expectedObj) assert(condition) // truthy assertion assert(!condition) // falsy assertion }) }) ``` ### Error Handling - Use `try/catch` for critical operations (file I/O, parsing) - Custom `trace` utility for error reporting with stack traces - `process.exit(1)` for fatal errors - `console.warn()` for non-fatal issues ### Async Patterns - Prefer `async/await` over raw promises - Use async generators (`async *emit()`) for streaming file output - Use `async-mutex` for concurrent build protection ## Project Structure ``` quartz/ ├── bootstrap-cli.mjs # CLI entry point ├── build.ts # Build orchestration ├── cfg.ts # Configuration types ├── components/ # Preact UI components │ ├── *.tsx # Components │ ├── scripts/ # Client-side scripts (*.inline.ts) │ └── styles/ # Component SCSS ├── plugins/ │ ├── transformers/ # Markdown AST transformers │ ├── filters/ # Content filters │ ├── emitters/ # Output generators │ └── types.ts # Plugin type definitions ├── processors/ # Build pipeline (parse/filter/emit) ├── util/ # Utility functions └── i18n/ # Internationalization (30+ locales) ``` ## Branch Workflow This is a fork of [jackyzha0/quartz](https://github.com/jackyzha0/quartz) with org-roam customizations. | Branch | Purpose | | ----------- | ------------------------------------------------ | | `main` | Clean mirror of upstream quartz — no custom code | | `org-roam` | Default branch — all customizations live here | | `feature/*` | Short-lived branches off `org-roam` | ### Pulling Upstream Updates ```bash git checkout main git fetch upstream git merge upstream/main git checkout org-roam git merge main # Resolve conflicts if any, then commit ``` ### Working on Features ```bash git checkout org-roam git checkout -b feature/my-feature # ... work ... git checkout org-roam git merge feature/my-feature git branch -d feature/my-feature ``` **Merge direction:** `upstream → main → org-roam → feature/*` ## Important Notes - **Client-side scripts**: Use `.inline.ts` suffix, bundled via esbuild - **Isomorphic code**: `quartz/util/path.ts` must not use Node.js APIs - **Incremental builds**: Plugins can implement `partialEmit` for efficiency - **Markdown flavors**: Supports Obsidian (`ofm.ts`) and Roam (`roam.ts`) syntax