diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..b8aedada9 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,242 @@ +# 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