From 41c05aba5884d144deccadc443672cc52759d84b Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Fri, 16 Jan 2026 19:21:22 +0100 Subject: [PATCH 01/16] Add Claude Code memory files for team collaboration Organize Claude Code memory files to enable team-wide AI assistance: - Move CLAUDE.md to .claude/CLAUDE.md (follows convention) - Add path-conditional rules for TypeScript, rendering, and formats - Add folder-specific CLAUDE.md for filters and tests directories - Update .gitignore to exclude personal Claude files Structure: - .claude/CLAUDE.md - Main project instructions (shared) - .claude/rules/ - Conditional rules by file pattern (shared) - .claude/commands/, .claude/docs/ - Personal (gitignored) - CLAUDE.local.md - Personal project prefs (gitignored) Rules added: - typescript/deno-conventions.md - Deno/TS patterns for src/**/*.ts - rendering/render-pipeline.md - Render system for src/command/render/** - formats/format-handlers.md - Format system for src/format/** Folder CLAUDE.md added: - src/resources/filters/CLAUDE.md - Lua filter system navigation - tests/CLAUDE.md - Test infrastructure quick reference Co-Authored-By: Claude Opus 4.5 --- .claude/CLAUDE.md | 295 +++++++++++++++++++ .claude/rules/formats/format-handlers.md | 262 ++++++++++++++++ .claude/rules/rendering/render-pipeline.md | 179 +++++++++++ .claude/rules/typescript/deno-conventions.md | 212 +++++++++++++ .gitignore | 8 + src/resources/filters/CLAUDE.md | 177 +++++++++++ tests/CLAUDE.md | 176 +++++++++++ 7 files changed, 1309 insertions(+) create mode 100644 .claude/CLAUDE.md create mode 100644 .claude/rules/formats/format-handlers.md create mode 100644 .claude/rules/rendering/render-pipeline.md create mode 100644 .claude/rules/typescript/deno-conventions.md create mode 100644 src/resources/filters/CLAUDE.md create mode 100644 tests/CLAUDE.md diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 0000000000..12f13f8dd3 --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1,295 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Quarto is an open-source scientific and technical publishing system built on Pandoc. The CLI is written in TypeScript/Deno with Lua filters for document processing. + +### Versions + +Latest stable is the version at: . Latest prerelease is at: + +## Setup & Configuration + +### Initial Setup + +```bash +# Clone and configure (downloads Deno, dependencies, and sets up symlink) +./configure.sh # Linux/macOS +./configure.cmd # Windows + +# The configure script: +# - Downloads and installs Deno to package/dist/bin/tools/ +# - Downloads Deno standard library +# - Runs quarto-bld configure +# - Vendors TypeScript dependencies +# - Creates symlink to quarto in your PATH +``` + +After configuration, the development version can be run via: + +- `quarto` command (if symlink configured) +- `package/dist/bin/quarto` (Linux/macOS) or `package/dist/bin/quarto.cmd` (Windows) + +### Configuration Files + +- `configuration` - Version numbers for all binary and JavaScript dependencies (Deno, Pandoc, Dart Sass, etc.) +- `deno.jsonc` - Auto-generated Deno configuration (see dev-docs/update-deno_jsonc.md) +- `src/import_map.json` - Deno import mappings +- `src/dev_import_map.json` - Development import mappings + +## Building & Testing + +### Build Commands + +```bash +# Run the build script (TypeScript-based) +package/src/quarto-bld configure # Configure/bootstrap +package/src/quarto-bld prepare # Prepare distribution +``` + +### Building Schemas and Artifacts + +Use the `dev-call` command with `build-artifacts` argument: + +```bash +# Linux/macOS +package/dist/bin/quarto dev-call build-artifacts + +# Windows +package/dist/bin/quarto.cmd dev-call build-artifacts +``` + +This command regenerates: + +- JSON schemas in `src/resources/schema/json-schemas.json` +- Zod schemas in `src/resources/types/zod/schema-types.ts` +- TypeScript type definitions in `src/resources/types/schema-types.ts` +- Editor tooling files (VSCode IntelliSense, YAML intelligence) + +### Running Tests + +```bash +cd tests + +# Linux/macOS +./run-tests.sh # Run all tests +./run-tests.sh smoke/extensions/extension-render-doc.test.ts # Run specific test +./run-tests.sh smoke/extensions/ # Run test directory +./run-tests.sh docs/smoke-all/2023/01/04/issue-3847.qmd # Run smoke-all test + +# Windows (PowerShell 7+) +.\run-tests.ps1 # Run all tests +.\run-tests.ps1 smoke/extensions/extension-render-doc.test.ts # Run specific test +.\run-tests.ps1 docs/smoke-all/2023/01/04/issue-3847.qmd # Run smoke-all test +``` + +**Test Dependencies:** Tests require R, Python, and Julia. Run `configure-test-env.sh` (or `.ps1`) to set up test environments using renv (R), uv (Python), and Pkg.jl (Julia). + +**Skip test configuration:** Set `QUARTO_TESTS_NO_CONFIG=true` to skip dependency setup when running tests. + +### Test Structure + +- `tests/unit/` - Unit tests +- `tests/integration/` - Integration tests +- `tests/smoke/` - Smoke tests +- `tests/docs/` - Test fixtures and sample documents +- `tests/docs/smoke-all/` - Document-based tests using `_quarto` YAML key + +See `tests/README.md` for comprehensive test documentation. + +### Feature Format Matrix + +The feature format matrix in `dev-docs/feature-format-matrix/` documents and tests feature support across all output formats. + +- Test documents organized by feature in `qmd-files/` subdirectories +- Quality ratings in format metadata: `0` (broken/partial), `1` (good), `2` (excellent) +- Runs on CI via `.github/workflows/test-ff-matrix.yml` + +## Architecture + +### Entry Point & Commands + +- `src/quarto.ts` - Main CLI entry point +- `src/command/command.ts` - Command registration +- Commands are organized in `src/command/*/cmd.ts` files: + - `render/` - Core rendering functionality + - `preview/` - Live preview server + - `publish/` - Publishing to various platforms + - `create/` and `add/` - Project/extension scaffolding + - `tools/`, `install/`, `check/` - Utilities + +### Core Systems + +**Project System** (`src/project/`) + +- `types/` - Project type implementations (book, website, manuscript, etc.) +- Project types are registered via `project/types/register.ts` +- Each type defines metadata, rendering behavior, and output structure + +**Single-File Rendering with `--output-dir` (Non-obvious variant)** + +When `--output-dir` is used without a project file, Quarto creates a "synthetic" project context: + +- Triggers when: `quarto render file.qmd --output-dir output/` and no `_quarto.yml` exists +- Creates temporary `.quarto` directory to manage the render +- Uses full `renderProject()` path, NOT `singleFileProjectContext()` +- `forceClean` flag in `RenderOptions` signals this is temporary and needs cleanup +- After rendering: closes file handles then removes `.quarto` directory +- Key locations: + - Creation: [render-shared.ts:52-58](src/command/render/render-shared.ts#L52-L58) + - Cleanup: [project.ts:889-907](src/command/render/project.ts#L889-L907) +- Related issues: #9745 (avoid leaving `.quarto` debris), #13625 (Windows file locking) +- Test coverage: [tests/smoke/render/render-output-dir.test.ts](tests/smoke/render/render-output-dir.test.ts) + +**Format System** (`src/format/`) + +- Format handlers for different output types (HTML, PDF, DOCX, reveal.js, etc.) +- `formats.ts` - Format registry and resolution +- `format-handlers.ts` - Common format handling logic + +**Filter System** (`src/resources/filters/`) + +- Lua filters process Pandoc AST during rendering +- `main.lua` - Entry point for filter chain +- Organized by function: `crossref/`, `layout/`, `quarto-pre/`, `quarto-post/`, `quarto-finalize/` +- Custom AST nodes in `customnodes/` +- Common utilities in `common/` + +**Execution Engines** (`src/execute/`) + +- Integration with Jupyter, Knitr, and Observable for code execution +- Engine-specific handling for Python, R, Julia, and JavaScript + +**Resources** (`src/resources/`) + +- Static assets, templates, and bundled libraries +- Format-specific resources (HTML, PDF, reveal.js templates) +- Extensions (Confluence, Docusaurus, etc.) +- Pandoc datadir customizations + +### Key Subsystems + +**Preview System** (`src/preview/`) + +- Development server with live reload +- Watches for file changes and re-renders + +**Publishing System** (`src/publish/`) + +- Platform-specific publishers (Netlify, GitHub Pages, Confluence, etc.) +- Account management and deployment logic + +**Extension System** (`src/extension/`) + +- Quarto extensions (filters, formats, shortcodes) +- Extension discovery, installation, and management + +### Package/Distribution + +- `package/` - Packaging and distribution scripts +- `package/src/quarto-bld` - Build orchestration script (TypeScript) +- Platform-specific packaging in `package/src/{linux,macos,windows}/` + +## Development Patterns + +### Debugging Flaky Tests + +Comprehensive methodology for debugging flaky tests documented in: https://gist.github.com/cderv/77405f5a5ea0c1db38693159c4a260dd + +Key phases: +1. Reproduce locally (outside CI) +2. Binary search to isolate culprit test +3. Narrow down within test file +4. Understand state change +5. Identify root cause +6. Verify solution + +### TypeScript/Deno Conventions + +- Use Deno-native APIs (avoid Node.js APIs) +- Import maps resolve dependencies (see `src/import_map.json`) +- Cliffy library used for CLI parsing +- File paths in imports must include `.ts` extension + +### Lua Filter Development + +- Filters run during Pandoc processing pipeline +- Use `quarto` Lua module for Quarto-specific APIs +- Common utilities in `src/resources/filters/common/` +- Filters are chained together in `main.lua` +- Documentation: + +### Adding New Commands + +1. Create command file in `src/command//cmd.ts` +2. Export command using Cliffy's `Command` API +3. Register in `src/command/command.ts` + +### Adding New Project Types + +1. Create implementation in `src/project/types//` +2. Implement `ProjectType` interface +3. Register in `src/project/types/register.ts` + +### Adding New Formats + +1. Create format definition in `src/format//` +2. Implement format handler +3. Register in `src/format/imports.ts` + +### LaTeX Error Detection + +LaTeX error pattern maintenance is documented in [dev-docs/tinytex-pattern-maintenance.md](dev-docs/tinytex-pattern-maintenance.md). + +- Patterns inspired by TinyTeX's comprehensive regex.json +- Automated daily verification workflow checks for TinyTeX pattern updates +- Pattern location: `src/command/render/latexmk/parse-error.ts` +- Verification workflow: `.github/workflows/verify-tinytex-patterns.yml` + +## Important Conventions + +- Main branch: `main` +- Version defined in `configuration` file in `QUARTO_VERSION` field +- Binary dependencies (Deno, Pandoc, etc.) versions in `configuration` +- Use `quarto-bld` for build operations, not direct Deno commands +- Lua filters use Pandoc's filter infrastructure +- TypeScript types for Lua APIs in `src/resources/lua-types/` + +### Changelog Conventions + +- Check `configuration` file for current version +- Add entries to `news/changelog-{version}.md` (e.g., `changelog-1.9.md` for version 1.9) +- Format: `- ([#issue](url)): Description.` +- Distinguish between: + - **Fixes**: Fixing bugs or broken functionality (e.g., "Fix `icon=false` not working...") + - **Enhancements**: Adding new features or support (e.g., "Add support for `icon=false`...") +- Group entries by format/feature area (typst, html, website, pdf, etc.) + +## Key File Paths + +- Quarto binary: `package/dist/bin/quarto` (Linux/macOS) or `package/dist/bin/quarto.cmd` (Windows) +- Deno binary: `package/dist/bin/tools//deno` +- Distribution output: `package/dist/` +- Vendored dependencies: `src/vendor/` + +## Documentation + +- Documentation is at with a sitemap at +- Prerelease docs: for features in dev versions +- Dev documentation in `dev-docs/` includes: + - Checklists for releases and backports + - Dependency update procedures + - Internals guides + - Performance monitoring + +## Contributing + +See CONTRIBUTING.md for pull request guidelines. Significant changes require a signed contributor agreement (individual or corporate). + +## Additional Resources + +- **LLM Documentation**: `llm-docs/` contains AI-specific guidance for working with the codebase +- **Rules Files**: `.claude/rules/` contains conditional guidance for specific file patterns +- **DeepWiki**: for AI-indexed documentation diff --git a/.claude/rules/formats/format-handlers.md b/.claude/rules/formats/format-handlers.md new file mode 100644 index 0000000000..6ad029730f --- /dev/null +++ b/.claude/rules/formats/format-handlers.md @@ -0,0 +1,262 @@ +--- +paths: + - "src/format/**/*" +--- + +# Format System + +Guidance for working with the Quarto format system. + +## Architecture Overview + +``` +Format Resolution Flow: +┌──────────────────────────────────────────────────┐ +│ defaultWriterFormat(formatString) [formats.ts] │ +│ │ +│ 1. Check registered handlers (writerFormatHandlers) +│ 2. Fall back to built-in switch statement │ +│ 3. Apply format variants (mergeFormatVariant) │ +└──────────────────────────────────────────────────┘ +``` + +## Key Files + +| File | Purpose | +|------|---------| +| `formats.ts` | Central `defaultWriterFormat()` resolver | +| `format-handlers.ts` | Handler registration API | +| `formats-shared.ts` | Factory functions (`createFormat`, `createHtmlFormat`) | +| `imports.ts` | Side-effect imports for format registration | + +## Format Handler Pattern + +Modern formats use registration to avoid circular dependencies: + +```typescript +// src/format//format-.ts +import { registerWriterFormatHandler } from "../format-handlers.ts"; + +function myFormat(): Format { + return createFormat("My Format", "html", { + pandoc: { to: "html" }, + render: { /* ... */ }, + // ... + }); +} + +// Register at module scope (runs on import) +registerWriterFormatHandler((format) => { + if (format === "myformat") { + return { + format: myFormat(), + pandocTo: "html", // Optional: override Pandoc writer + }; + } +}); +``` + +Then add to `imports.ts`: +```typescript +import "./myformat/format-myformat.ts"; +``` + +## Format Structure + +```typescript +interface Format { + identifier: FormatIdentifier; // Display name, base/target format + render: FormatRender; // Rendering options (keep-tex, etc.) + execute: FormatExecute; // Execution (fig-width, echo, cache) + pandoc: FormatPandoc; // Pandoc args (to, from, template) + language: FormatLanguage; // Localized strings + metadata: Metadata; // Document metadata + + // Optional hooks + resolveFormat?: (format: Format) => void; + formatExtras?: (...) => Promise; + extensions?: { book?: BookExtension }; +} +``` + +## Factory Functions + +**Base format:** +```typescript +import { createFormat } from "../formats-shared.ts"; + +const format = createFormat( + "My Format", // Display name + "html", // File extension + baseFormat1, // Formats to merge (in order) + baseFormat2, +); +``` + +**HTML-based:** +```typescript +import { createHtmlFormat, htmlFormat } from "../formats-shared.ts"; + +const format = createHtmlFormat("My HTML", 7, 5); // figwidth, figheight +``` + +**Extend existing:** +```typescript +import { mergeConfigs } from "../../core/config.ts"; + +const format = mergeConfigs( + htmlFormat(7, 5), // Base format + { + render: { echo: false }, + execute: { warning: false }, + }, +); +``` + +## FormatExtras Hook + +The `formatExtras()` hook adds format-specific processing: + +```typescript +formatExtras: async ( + input: string, + markdown: string, + flags: RenderFlags, + format: Format, + libDir: string, + services: RenderServices, + offset?: string, + project?: ProjectContext, + quiet?: boolean, +) => { + return { + pandoc: { /* additional pandoc args */ }, + html: { + dependencies: [/* scripts, stylesheets */], + sass: [/* Sass bundles */], + }, + postprocessors: [/* DOM manipulation functions */], + // ... + }; +}, +``` + +## Adding a New Format + +**Step 1:** Create format file + +```typescript +// src/format/myformat/format-myformat.ts +import { createFormat } from "../formats-shared.ts"; +import { registerWriterFormatHandler } from "../format-handlers.ts"; +import { mergeConfigs } from "../../core/config.ts"; + +function myFormat(): Format { + return mergeConfigs( + createFormat("My Format", "html"), + { + pandoc: { + to: "html", + // format-specific pandoc options + }, + execute: { + echo: false, // example: hide code by default + }, + }, + ); +} + +registerWriterFormatHandler((format) => { + if (format === "myformat") { + return { + format: myFormat(), + pandocTo: "html", + }; + } +}); +``` + +**Step 2:** Add to imports + +```typescript +// src/format/imports.ts +import "./myformat/format-myformat.ts"; +``` + +**Step 3:** Test + +```yaml +# test.qmd +--- +format: myformat +--- +Test content +``` + +## Common Patterns + +**Format extending another:** +```typescript +// Dashboard extends HTML +const dashboardFormat = mergeConfigs( + htmlFormat(7, 5), + { + identifier: { displayName: "Dashboard", ... }, + render: { echo: false }, + // Override formatExtras to add dashboard processing + }, +); +``` + +**Wrapping formatExtras:** +```typescript +const baseExtras = baseFormat.formatExtras; +return { + ...baseFormat, + formatExtras: async (...args) => { + const extras = baseExtras ? await baseExtras(...args) : {}; + return { + ...extras, + postprocessors: [ + ...(extras.postprocessors || []), + myPostprocessor, + ], + }; + }, +}; +``` + +**Binary output (PDF):** +```typescript +const pdfFormat = createFormat("PDF", "pdf", { + pandoc: { to: "latex" }, // Pandoc produces LaTeX + render: { "keep-tex": false }, + // postprocessors convert .tex → .pdf via latexmk +}); +``` + +## Directory Structure + +``` +src/format/ +├── format-handlers.ts # Registration API +├── formats.ts # Central resolver +├── formats-shared.ts # Factory functions +├── imports.ts # Initialization +├── html/ # HTML + variants +├── reveal/ # Reveal.js +├── dashboard/ # Dashboard +├── pdf/ # PDF, Beamer +├── typst/ # Typst +├── docx/ # DOCX +└── ... +``` + +## Key Concepts + +1. **Registration order** - Handlers checked before hardcoded formats +2. **Format composition** - Use `mergeConfigs()` to layer formats +3. **Pandoc writer override** - Handler can map format → different pandoc writer +4. **Lazy initialization** - Side-effect imports prevent circular deps +5. **formatExtras** - Pre-Pandoc setup (dependencies, sass) +6. **postprocessors** - Post-Pandoc file manipulation diff --git a/.claude/rules/rendering/render-pipeline.md b/.claude/rules/rendering/render-pipeline.md new file mode 100644 index 0000000000..8f30f11c09 --- /dev/null +++ b/.claude/rules/rendering/render-pipeline.md @@ -0,0 +1,179 @@ +--- +paths: + - "src/command/render/**/*" + - "src/render/**/*" +--- + +# Render Pipeline + +Guidance for working with the Quarto render system. + +## Render Flow Overview + +``` +CLI (cmd.ts) + ↓ +render() (render-shared.ts) + ↓ +┌─────────────────────────────────────────────────┐ +│ Decision: What type of render? │ +│ │ +│ 1. Project file exists + file in project │ +│ → renderProject() │ +│ │ +│ 2. --output-dir without project file │ +│ → Synthetic project → renderProject() │ +│ │ +│ 3. Single file │ +│ → singleFileProjectContext() → renderFiles() │ +└─────────────────────────────────────────────────┘ + ↓ +Per-file pipeline: Execute → Pandoc → Postprocess +``` + +## Key Files and Responsibilities + +| File | Purpose | +|------|---------| +| `cmd.ts` | CLI command handler, argument parsing | +| `render-shared.ts` | Top-level orchestration, render type decision | +| `render-contexts.ts` | Format resolution, metadata merging | +| `render-files.ts` | Per-file render pipeline, freezer logic | +| `project.ts` | Project-level orchestration, output management | +| `render.ts` | Core `renderPandoc()`, postprocessors | +| `types.ts` | Type definitions (`RenderOptions`, `RenderContext`) | + +## Synthetic Project Context Pattern + +**Critical non-obvious behavior** for `--output-dir` without a project file: + +When a user runs: +```bash +quarto render file.qmd --output-dir output/ +``` + +And no `_quarto.yml` exists, Quarto: + +1. Creates a temporary `.quarto` directory +2. Uses full `renderProject()` path (NOT `singleFileProjectContext()`) +3. Sets `forceClean` flag to signal cleanup needed +4. After rendering: closes file handles, removes `.quarto` + +**Key locations:** +- Creation: `render-shared.ts:52-60` +- Cleanup: `project.ts:889-907` +- Flag: `types.ts:38` + +**Why this matters:** +- Prevents `.quarto` debris in non-project directories (#9745) +- Windows file locking requires careful cleanup ordering (#13625) + +```typescript +// render-shared.ts - Decision logic +let context = await projectContext(path, nbContext, options); + +if (!context && options.flags?.outputDir) { + // Create synthetic project for --output-dir + context = await projectContextForDirectory(path, nbContext, options); + options.forceClean = options.flags.clean !== false; +} + +if (context?.config && isProjectInputFile(path, context)) { + return renderProject(context, options, [path]); +} else { + context = await singleFileProjectContext(path, nbContext, options); + return renderFiles([{ path }], options, ...); +} +``` + +## Format Resolution + +Metadata merges in this order (later overrides earlier): + +1. **Project metadata** (`_quarto.yml`) +2. **Directory metadata** (`_metadata.yml`) +3. **File metadata** (YAML frontmatter) +4. **Extension formats** (custom extensions) +5. **Default writer format** (built-in defaults) + +Implemented in `render-contexts.ts`. + +## Execute → Pandoc → Postprocess + +**Phase 1: Execute** (`render-files.ts`) +- Check freezer for cached results +- Run execution engine (Jupyter, Knitr, etc.) +- Handle language cells +- Produce markdown + metadata + +**Phase 2: Pandoc** (`render.ts`) +- Merge includes from execute result +- Run `runPandoc()` with filters +- Generate output file + +**Phase 3: Postprocess** (`render.ts`) +- Engine postprocess +- HTML postprocessors (DOM manipulation) +- Generic postprocessors +- Recipe completion (LaTeX → PDF) +- Self-contained output +- Cleanup + +## Cleanup Patterns + +**Normal project:** +```typescript +// Controlled by --clean flag +if (options.clean && renderAll) { + cleanOutputDir(outputDir); +} +``` + +**Synthetic project (forceClean):** +```typescript +// project.ts - Must close handles before removing files +context.cleanup(); // Close file handles +safeRemoveSync(join(projDir, kQuartoScratch)); // Remove .quarto +``` + +**Critical for Windows:** Close handles before removing files to avoid "file in use" errors. + +## RenderContext Structure + +```typescript +interface RenderContext { + target: ExecutionTarget; // Input file metadata + options: RenderOptions; // Flags, services, pandocArgs + engine: ExecutionEngineInstance; // Jupyter, Knitr, etc. + format: Format; // Resolved format config + libDir: string; // Library directory path + project: ProjectContext; // Project context (may be synthetic) + active: boolean; // Is this format being rendered? +} +``` + +## Important Flags + +| Flag | Purpose | +|------|---------| +| `--output-dir` | Output directory (triggers synthetic project if no project file) | +| `--to` | Target format(s) | +| `--execute` / `--no-execute` | Control code execution | +| `--clean` / `--no-clean` | Control output cleanup | +| `forceClean` (internal) | Signals synthetic project cleanup | + +## Development Considerations + +When modifying render code: + +1. **Consider both paths** - Project render vs single-file render +2. **Synthetic cleanup** - `--output-dir` without project needs cleanup +3. **Windows file locking** - Close handles before removing files +4. **Format resolution** - Multi-level merge is complex +5. **Project types** - Book/website/manuscript customize behavior + +## Testing + +- Smoke tests: `tests/smoke/render/` +- Output-dir tests: `tests/smoke/render/render-output-dir.test.ts` +- Document tests: `tests/docs/smoke-all/` with `_quarto` metadata diff --git a/.claude/rules/typescript/deno-conventions.md b/.claude/rules/typescript/deno-conventions.md new file mode 100644 index 0000000000..c13c4dd7d9 --- /dev/null +++ b/.claude/rules/typescript/deno-conventions.md @@ -0,0 +1,212 @@ +--- +paths: + - "src/**/*.ts" +--- + +# TypeScript/Deno Development Conventions + +Guidance for developing TypeScript code in the quarto-cli codebase using Deno. + +## Import Patterns + +### Use Import Map Names + +```typescript +// ✅ Correct - use import map names +import { Command } from "cliffy/command/mod.ts"; +import { join, dirname } from "../deno_ral/path.ts"; + +// ❌ Wrong - never hardcode JSR/npm URLs in imports +import { join } from "jsr:/@std/path"; +import { z } from "npm:zod"; +``` + +### Always Include `.ts` Extensions + +Required for Deno module resolution: + +```typescript +// ✅ Correct +import { debug } from "../../deno_ral/log.ts"; + +// ❌ Wrong - no extension +import { debug } from "../../deno_ral/log"; +``` + +## Deno RAL (Runtime Abstraction Layer) + +Always import from `src/deno_ral/` rather than using standard library directly: + +```typescript +// ✅ Correct - use abstraction layer +import { join, dirname } from "../deno_ral/path.ts"; +import { existsSync, ensureDirSync } from "../deno_ral/fs.ts"; +import { info, warning, error } from "../deno_ral/log.ts"; + +// ❌ Wrong - direct std lib import +import { join } from "jsr:/@std/path"; +``` + +**Key deno_ral modules:** +- `deno_ral/fs.ts` - File system operations +- `deno_ral/path.ts` - Path utilities +- `deno_ral/log.ts` - Logging (debug, info, warning, error) +- `deno_ral/platform.ts` - Platform detection (`isWindows`) +- `deno_ral/process.ts` - Process execution + +## Deno APIs vs Node.js + +**Use Deno APIs directly**, not Node.js equivalents: + +```typescript +// ✅ Correct - Deno APIs +Deno.env.get("PATH"); +Deno.cwd(); +Deno.readTextFileSync(path); +Deno.writeTextFileSync(path, content); +Deno.statSync(path); +Deno.makeTempDirSync({ prefix: "quarto" }); +Deno.removeSync(path, { recursive: true }); + +// ❌ Wrong - Node.js patterns +process.env.PATH; +process.cwd(); +fs.readFileSync(path); +``` + +## Sync vs Async + +**Prefer sync APIs** in CLI command handlers for simpler code flow: + +```typescript +// ✅ Typical CLI command - sync is fine +const content = Deno.readTextFileSync(path); +const parsed = JSON.parse(content); + +// ✅ Use async for I/O-heavy operations +const results = await Promise.all([ + fetchRemoteData(url1), + fetchRemoteData(url2), +]); +``` + +## Cliffy Command Pattern + +Commands follow this structure: + +```typescript +export const myCommand = new Command() + .name("command-name") + .description("What the command does") + .arguments("[input:string]") + .option("-f, --flag", "Flag description") + .option("-o, --output ", "Option with value") + .example("Basic usage", "quarto command input.qmd") + // deno-lint-ignore no-explicit-any + .action(async (options: any, input?: string) => { + // Implementation + }); +``` + +**Note:** Options must be typed as `any` due to Cliffy limitations. + +**Registration:** Export from `src/command//cmd.ts`, register in `src/command/command.ts`. + +## Error Handling + +Use custom error classes from `src/core/lib/error.ts`: + +```typescript +import { InternalError, ErrorEx } from "../core/lib/error.ts"; + +// Programming errors (bugs) +throw new InternalError("This should never happen"); + +// General errors with better stack traces +throw new ErrorEx("User-facing error message"); + +// Normalize unknown errors +try { + riskyOperation(); +} catch (e) { + const err = asErrorEx(e); + error(err.message); +} +``` + +## Lint Directives + +Use sparingly, only when necessary: + +```typescript +// deno-lint-ignore no-explicit-any +.action(async (options: any, input?: string) => { + // Cliffy requires any for options +}); +``` + +Common directives: +- `no-explicit-any` - For Cliffy options, JSON parsing +- `no-control-regex` - For regex with control characters + +## Path Handling + +```typescript +import { normalizePath } from "../core/path.ts"; +import { isWindows } from "../deno_ral/platform.ts"; + +// Normalize paths for consistent comparison +const normalized = normalizePath(Deno.cwd()); + +// Platform-specific logic +if (isWindows) { + // Windows-specific handling +} +``` + +## Common Utilities + +**Temp files:** (`src/core/temp.ts`) +```typescript +import { createTempContext, globalTempContext } from "../core/temp.ts"; + +// Scoped temp context with cleanup +const temp = createTempContext(); +try { + const tempFile = temp.createFile({ suffix: ".json" }); + // Use tempFile... +} finally { + temp.cleanup(); +} +``` + +**Process execution:** (`src/core/process.ts`) +```typescript +import { execProcess } from "../core/process.ts"; + +const result = await execProcess({ + cmd: ["pandoc", "--version"], + stdout: "piped", +}); +``` + +## Module Loading Order + +`src/quarto.ts` loads monkey patches first: +```typescript +import "./core/deno/monkey-patch.ts"; // Must be first! +// ... rest of imports +``` + +This ensures compatibility shims are loaded before any code runs. + +## Key Conventions Summary + +1. **Use import map names** - Never hardcode JSR/npm URLs +2. **Include `.ts` extensions** - Required for Deno resolution +3. **Import from deno_ral** - Not directly from std library +4. **Use Deno APIs** - No Node.js equivalents +5. **Prefer sync APIs** - Simpler code flow for CLI +6. **Type options as `any`** - Cliffy limitation +7. **Use custom error classes** - Better error handling +8. **Normalize paths** - Use `normalizePath()` for consistency diff --git a/.gitignore b/.gitignore index f43d068d2f..f7564b03d3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,13 @@ .DS_Store .idea + +# Claude Code files +CLAUDE.local.md +.claude/commands/ +.claude/docs/ +.claude/settings.local.json +.claude/.claude/ +.claude/.private-journal/ *.RData *.Rproj *.Rhistory diff --git a/src/resources/filters/CLAUDE.md b/src/resources/filters/CLAUDE.md new file mode 100644 index 0000000000..6aaf19009e --- /dev/null +++ b/src/resources/filters/CLAUDE.md @@ -0,0 +1,177 @@ +# Lua Filter System + +This directory contains Quarto's Lua filter system - a multi-stage document transformation pipeline that processes Pandoc AST through ~212 Lua files. + +## Quick Reference + +| Task | Location | +|------|----------| +| Add new filter | Edit `main.lua`, add `import()` + filter entry | +| Create custom node | `customnodes/` + `_quarto.ast.add_handler()` | +| Check format | `_quarto.format.isHtmlOutput()`, `.isLatexOutput()`, etc. | +| Logging | `warn()`, `error()`, `info()` from `common/log.lua` | +| Debug dump | `quarto.utils.dump(obj)` | +| Trace filters | `quarto dev-call show-ast-trace document.qmd` | + +## Directory Structure + +``` +filters/ +├── main.lua # Entry point - filter chain definition +├── mainstateinit.lua # Global state initialization +├── ast/ # Custom AST infrastructure +├── common/ # Shared utilities (~35 files) +├── modules/ # Reusable modules (require()) +├── customnodes/ # Custom node implementations +├── quarto-init/ # Initialization stage +├── normalize/ # Normalization stage +├── quarto-pre/ # Pre-processing (shortcodes, tables, etc.) +├── crossref/ # Cross-reference system +├── layout/ # Layout processing +├── quarto-post/ # Post-processing (format-specific) +└── quarto-finalize/ # Final cleanup +``` + +## Filter Execution Pipeline + +``` +1. INIT (quarto-init/) + ↓ +2. NORMALIZE (normalize/) + ↓ +3. PRE (quarto-pre/) - shortcodes, tables, code annotations + ↓ +4. CROSSREF (crossref/) - cross-references + ↓ +5. LAYOUT (layout/) - panels, columns + ↓ +6. POST (quarto-post/) - format-specific rendering + ↓ +7. FINALIZE (quarto-finalize/) - cleanup, dependencies +``` + +User filters can run between stages via entry points (`pre-ast`, `post-ast`, `pre-quarto`, etc.). + +## Custom Node System + +Quarto extends Pandoc AST with custom node types (callouts, tabsets, floats, etc.). + +**Lifecycle:** +1. **Parse** (quarto-pre): Detect special Div → create custom node +2. **Transform**: Filters manipulate custom nodes +3. **Render** (quarto-post): Convert back to Pandoc AST for output + +**Example custom node handler:** +```lua +_quarto.ast.add_handler({ + class_name = "callout", + ast_name = "Callout", + kind = "Block", + parse = function(div) ... end, + slots = { "title", "content" }, + constructor = function(tbl) ... end +}) +``` + +**Key files:** +- `ast/customnodes.lua` - Core system +- `customnodes/*.lua` - Individual implementations + +## Common Patterns + +**Format-specific rendering:** +```lua +if _quarto.format.isLatexOutput() then + return pandoc.RawBlock("latex", "\\begin{...}...") +elseif _quarto.format.isHtmlOutput() then + return pandoc.Div(...) +end +``` + +**Walking custom nodes:** +```lua +doc = _quarto.ast.walk(doc, filter) -- Handles custom nodes +``` + +**Checking node types:** +```lua +if is_custom_node(node, "Callout") then ... end +if is_regular_node(node, "Div") then ... end -- NOT custom node +``` + +**Reading options:** +```lua +local value = option("callout-icon", true) +local engine = param("execution-engine") +``` + +## Adding a New Filter + +1. Choose stage: `quarto-pre/`, `quarto-post/`, etc. +2. Create filter file: + ```lua + -- quarto-pre/my-feature.lua + function my_feature() + return { + Div = function(div) + if div.classes:includes("my-feature") then + return process(div) + end + end + } + end + ``` +3. Import in `main.lua`: + ```lua + import("./quarto-pre/my-feature.lua") + ``` +4. Add to filter list in `main.lua`: + ```lua + { name = "pre-my-feature", filter = my_feature() } + ``` + +## Debugging + +**Filter tracing (recommended):** +```bash +# Linux/macOS +package/dist/bin/quarto dev-call show-ast-trace document.qmd + +# Windows +package/dist/bin/quarto.cmd dev-call show-ast-trace document.qmd +``` + +**Print debugging:** +```lua +quarto.utils.dump(node) -- Pretty-print any object +warn("Debug: " .. tostring(value)) -- Appears in console +``` + +**AST diagram:** +```bash +quarto dev-call make-ast-diagram document.qmd +``` + +## Common Gotchas + +1. **Custom vs regular nodes**: Use `is_regular_node()` to exclude custom nodes +2. **Slot assignment**: Use proxy pattern, don't assign directly to `.content` +3. **Filter returns**: Return `nil` to continue, return value to replace +4. **Lua messages**: `warn()` appears as INFO level on TypeScript side + +## Key Files + +| File | Purpose | +|------|---------| +| `main.lua` | Filter chain definition (~725 lines) | +| `common/log.lua` | Logging utilities | +| `common/debug.lua` | Debug utilities (`dump`, `tdump`) | +| `common/format.lua` | Format detection | +| `common/options.lua` | Metadata option reading | +| `ast/customnodes.lua` | Custom node system | + +## Related Documentation + +- **Lua API**: +- **Filter tracing**: `dev-docs/lua-filter-trace-viewer.qmd` +- **Rule file**: `.claude/rules/filters/lua-development.md` diff --git a/tests/CLAUDE.md b/tests/CLAUDE.md new file mode 100644 index 0000000000..440330fe79 --- /dev/null +++ b/tests/CLAUDE.md @@ -0,0 +1,176 @@ +# Test Infrastructure + +This directory contains Quarto's test suite. For comprehensive documentation, see `README.md` in this directory. + +## Quick Reference + +### Running Tests + +```bash +cd tests + +# Linux/macOS +./run-tests.sh # All tests +./run-tests.sh smoke/render/render.test.ts # Specific test file +./run-tests.sh docs/smoke-all/path/to/test.qmd # Smoke-all document + +# Windows (PowerShell 7+) +.\run-tests.ps1 # All tests +.\run-tests.ps1 smoke/render/render.test.ts # Specific test file +.\run-tests.ps1 docs/smoke-all/path/to/test.qmd # Smoke-all document +``` + +**Skip dependency configuration:** +```bash +# Linux/macOS +QUARTO_TESTS_NO_CONFIG="true" ./run-tests.sh test.ts + +# Windows +$env:QUARTO_TESTS_NO_CONFIG=$true; .\run-tests.ps1 test.ts +``` + +## Test Structure + +``` +tests/ +├── unit/ # Unit tests +├── integration/ # Integration tests +├── smoke/ # Smoke tests (.test.ts files) +├── docs/ # Test fixtures +│ └── smoke-all/ # Document-based tests (.qmd files) +├── verify.ts # Verification functions +└── utils.ts # Test utilities +``` + +## Smoke-All Test Format + +Document-based tests use `_quarto.tests` YAML metadata: + +```yaml +--- +title: My Test +_quarto: + tests: + html: + ensureHtmlElements: + - ['div.callout'] # Must exist + - ['div.error', false] # Must NOT exist + typst: + ensureTypstFileRegexMatches: + - ['#callout\('] # Pattern must match + - ['ERROR'] # Pattern must NOT match +--- +``` + +**Requires intermediate file preservation:** +- Typst: Add `keep-typ: true` to frontmatter +- LaTeX: Add `keep-tex: true` to frontmatter + +## Key Verification Functions + +From `verify.ts`: + +| Function | Purpose | +|----------|---------| +| `ensureHtmlElements` | Check HTML element presence | +| `ensureTypstFileRegexMatches` | Check Typst source patterns | +| `ensureLatexFileRegexMatches` | Check LaTeX source patterns | +| `ensureFileRegexMatches` | Check any file content | +| `printsMessage` | Verify render output messages | +| `ensureSnapshotMatches` | Compare against saved snapshot | +| `noErrors`, `noErrorsOrWarnings` | Verify clean rendering | + +## Message Verification + +**Important**: Lua `warn()` appears as `level: INFO` on TypeScript side: + +```yaml +_quarto: + tests: + html: + # Verify Lua warning appears + printsMessage: { level: INFO, regex: 'WARNING(.*)text' } + + # Verify message does NOT appear + printsMessage: { level: INFO, regex: 'ERROR', negate: true } +``` + +## Test Control Metadata + +```yaml +_quarto: + tests: + run: + skip: true # Skip unconditionally + skip: "Reason for skipping" # Skip with message + ci: false # Skip on CI only + os: darwin # Run only on macOS + os: [windows, darwin] # Run on Windows or macOS + not_os: linux # Don't run on Linux +``` + +Valid OS values: `linux`, `darwin`, `windows` + +## Test Organization + +**Issue-based tests:** `tests/docs/smoke-all/YYYY/MM/DD/` +- Filename: Issue number (e.g., `13589.qmd`) +- Multiple related: `13589.qmd`, `13589-valid.qmd` +- Reference issue in title: `"Bug with feature (#13589)"` + +**Feature tests:** `tests/docs/smoke-all//` +- Examples: `typst/`, `crossrefs/`, `html/` + +## Common Patterns + +### Regex Best Practices + +**Be specific to avoid template matches:** +```yaml +# Bad - matches template boilerplate +- ['#figure\('] + +# Good - matches specific document structure +- ['#figure\(\[(\r\n?|\n)#block\[(\r\n?|\n)#callout'] +``` + +**Line breaks:** +- `\s*` - Zero or more whitespace (spaces, tabs, newlines) +- `(\r\n?|\n)` - Only line breaks (Windows or Unix) + +### Snapshot Testing + +```yaml +_quarto: + tests: + html: + ensureSnapshotMatches: [] +``` + +Save snapshot file as `output.html.snapshot` alongside expected output. + +## Dependencies + +Tests require R, Python, and Julia. Run configuration script: +```bash +# Linux/macOS +./configure-test-env.sh + +# Windows +.\configure-test-env.ps1 +``` + +Or set `QUARTO_TESTS_NO_CONFIG=true` to skip. + +## Debugging Tests + +1. **Run single test** to isolate failures +2. **Check render output** - tests capture stdout/stderr +3. **Keep intermediate files** - Add `keep-typ: true` or `keep-tex: true` +4. **VSCode debugging** - See `.vscode/launch.json` configuration + +## Related Documentation + +- **Full test docs**: `tests/README.md` +- **Flaky test debugging**: https://gist.github.com/cderv/77405f5a5ea0c1db38693159c4a260dd +- **Rule file**: `.claude/rules/testing/smoke-all-format.md` From c116a4de22aa5660a41dbe4b2796be6e7ee9e90e Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Tue, 20 Jan 2026 17:14:01 +0100 Subject: [PATCH 02/16] Add path-based rule files for Claude Code - Add Lua development conventions (.claude/rules/filters/lua-development.md) - Add testing rules for all test types: - Unit tests (.claude/rules/testing/unit-tests.md) - Smoke tests (.claude/rules/testing/smoke-tests.md) - Smoke-all tests (.claude/rules/testing/smoke-all-tests.md) - Playwright tests (.claude/rules/testing/playwright-tests.md) - Refactor tests/CLAUDE.md to lean overview with pointers to rule files - Add "Maintaining Memory Files" section to main CLAUDE.md These rule files load automatically based on paths: frontmatter when Claude Code reads files matching those patterns. Co-Authored-By: Claude Opus 4.5 --- .claude/CLAUDE.md | 18 ++ .claude/rules/filters/lua-development.md | 234 ++++++++++++++++++++++ .claude/rules/testing/playwright-tests.md | 97 +++++++++ .claude/rules/testing/smoke-all-tests.md | 118 +++++++++++ .claude/rules/testing/smoke-tests.md | 88 ++++++++ .claude/rules/testing/unit-tests.md | 109 ++++++++++ tests/CLAUDE.md | 176 ++++------------ 7 files changed, 699 insertions(+), 141 deletions(-) create mode 100644 .claude/rules/filters/lua-development.md create mode 100644 .claude/rules/testing/playwright-tests.md create mode 100644 .claude/rules/testing/smoke-all-tests.md create mode 100644 .claude/rules/testing/smoke-tests.md create mode 100644 .claude/rules/testing/unit-tests.md diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 12f13f8dd3..3d8acc7892 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -288,6 +288,24 @@ LaTeX error pattern maintenance is documented in [dev-docs/tinytex-pattern-maint See CONTRIBUTING.md for pull request guidelines. Significant changes require a signed contributor agreement (individual or corporate). +## Maintaining Memory Files + +This project uses Claude Code memory files for AI-assisted development. When updating memory files: + +- **Add new feature area?** Create `.claude/rules//feature-name.md` with `paths:` frontmatter +- **Update existing feature?** Edit the relevant rule file or subfolder CLAUDE.md +- **Deep dive doc needed?** Place it in `llm-docs/` and reference from rules + +**Memory file types:** + +| Location | When Loaded | Use For | +|----------|-------------|---------| +| `.claude/CLAUDE.md` | Always | Project overview, essential commands | +| `.claude/rules//` | When paths match | Feature-specific conventions | +| `/CLAUDE.md` | When reading files in folder | Deep documentation for that area | + +**Personal overrides:** Create `CLAUDE.local.md` (gitignored) for personal preferences like preferred shell syntax or workflow customizations. This file is loaded alongside the project CLAUDE.md but won't be committed. + ## Additional Resources - **LLM Documentation**: `llm-docs/` contains AI-specific guidance for working with the codebase diff --git a/.claude/rules/filters/lua-development.md b/.claude/rules/filters/lua-development.md new file mode 100644 index 0000000000..d8e0a0c7c6 --- /dev/null +++ b/.claude/rules/filters/lua-development.md @@ -0,0 +1,234 @@ +--- +paths: + - "src/resources/filters/**/*.lua" +--- + +# Lua Filter Development Conventions + +Guidance for developing Lua filters in Quarto's filter system. + +## Module Loading + +### Use `import()` for Filter Files + +```lua +-- ✅ Correct - import from main.lua +import("./quarto-pre/shortcodes.lua") + +-- ❌ Wrong - require is for modules only +require("./quarto-pre/shortcodes") +``` + +### Use `require()` for Modules + +```lua +-- ✅ Correct - modules from modules/ +local patterns = require("modules/patterns") +local md = require("modules/md") + +-- ❌ Wrong - using import for modules +import("./modules/patterns.lua") +``` + +## Custom Node Patterns + +### Walking Custom Nodes + +Always use `_quarto.ast.walk()` to properly handle custom nodes: + +```lua +-- ✅ Correct - handles custom nodes +doc = _quarto.ast.walk(doc, { + Callout = function(callout) + -- Process callout + end +}) + +-- ❌ Wrong - misses custom nodes +pandoc.walk_block(div, filter) +``` + +### Checking Node Types + +```lua +-- Check if custom node of specific type +if is_custom_node(node, "Callout") then + -- Handle callout custom node +end + +-- Check if regular Pandoc node (NOT custom node) +if is_regular_node(node, "Div") then + -- Handle regular Div +end + +-- Check custom node by presence of is_custom_node flag +if node.is_custom_node then + -- It's some custom node type +end +``` + +### Slot Assignment + +Use the proxy pattern for slot modification: + +```lua +-- ✅ Correct - proxy pattern +local new_callout = callout:clone() +new_callout.content = modified_content +return new_callout + +-- ❌ Wrong - direct assignment may not work +callout.content = modified_content +return callout +``` + +## Format Detection + +Use `_quarto.format` for format checks: + +```lua +-- HTML output (includes HTML-based formats) +if _quarto.format.isHtmlOutput() then ... end + +-- LaTeX/PDF output +if _quarto.format.isLatexOutput() then ... end + +-- Typst output +if _quarto.format.isTypstOutput() then ... end + +-- Word/DOCX output +if _quarto.format.isDocxOutput() then ... end + +-- Reveal.js slides +if _quarto.format.isRevealJsOutput() then ... end + +-- Dashboard format +if _quarto.format.isDashboardOutput() then ... end +``` + +## Options and Parameters + +```lua +-- Read metadata option with default +local show_icon = option("callout-icon", true) + +-- Read execution parameter +local engine = param("execution-engine") + +-- Read option with nil fallback +local custom = option("my-option") +if custom ~= nil then + -- Option was set +end +``` + +## Logging + +Use logging functions from `common/log.lua`: + +```lua +-- Debug info (verbose) +info("Processing element: " .. el.t) + +-- Warnings (appear as INFO on TypeScript side) +warn("Deprecated feature used") + +-- Errors +error("Invalid configuration") + +-- Conditional debug output +if quarto.log.debug then + quarto.utils.dump(node) +end +``` + +## Filter Return Values + +```lua +function my_filter() + return { + Div = function(div) + -- Return nil to continue (no change) + if not should_process(div) then + return nil + end + + -- Return new element to replace + return pandoc.Div(modified_content) + + -- Return empty list to remove element + -- return {} + end + } +end +``` + +## Common Utilities + +### String Operations + +```lua +-- String matching +if string.match(text, "pattern") then ... end + +-- String substitution +local result = string.gsub(text, "old", "new") + +-- Check class presence +if div.classes:includes("callout") then ... end + +-- Check attribute +local value = div.attributes["data-foo"] +``` + +### Pandoc Helpers + +```lua +-- Create elements +local div = pandoc.Div(content, pandoc.Attr(id, classes, attributes)) +local span = pandoc.Span(inlines) +local para = pandoc.Para(inlines) + +-- Raw output +local raw = pandoc.RawBlock("html", "
...
") +local raw = pandoc.RawInline("latex", "\\textbf{}") + +-- Stringify content +local text = pandoc.utils.stringify(inlines) +``` + +## Debugging + +```lua +-- Pretty-print any object +quarto.utils.dump(node) + +-- Type checking +print("Type: " .. type(obj)) +print("Pandoc type: " .. (obj.t or "none")) + +-- Trace execution +warn("Reached checkpoint: " .. checkpoint_name) +``` + +## Filter Chain Integration + +When adding filters to `main.lua`: + +```lua +-- Each filter gets a name and filter function +{ name = "pre-my-feature", filter = my_feature() } + +-- Filter order matters - check dependencies +-- Filters in same stage run in order defined +``` + +## Key Conventions Summary + +1. **Use `import()` for filters** - `require()` for modules only +2. **Use `_quarto.ast.walk()`** - Not `pandoc.walk_*` for custom nodes +3. **Check node types carefully** - `is_custom_node()` vs `is_regular_node()` +4. **Use proxy pattern** - For modifying custom node slots +5. **Use `_quarto.format`** - For format detection +6. **Return `nil` to continue** - Return value replaces element +7. **`warn()` = INFO level** - On TypeScript side diff --git a/.claude/rules/testing/playwright-tests.md b/.claude/rules/testing/playwright-tests.md new file mode 100644 index 0000000000..229087cc62 --- /dev/null +++ b/.claude/rules/testing/playwright-tests.md @@ -0,0 +1,97 @@ +--- +paths: + - "tests/integration/playwright/**/*.spec.ts" + - "tests/integration/playwright/**/*.ts" +--- + +# Playwright Tests + +Browser-based tests for interactive features. Tests live in `tests/integration/playwright/tests/`. + +## Local Development Workflow + +**Don't run `playwright-tests.test.ts` locally** - it renders ALL test documents which is slow. + +Instead, follow this pattern: + +```bash +# 1. Create/edit test documents +# Location: tests/docs/playwright// + +# 2. Render the documents you're working on +./package/dist/bin/quarto render tests/docs/playwright/html/tabsets/test.qmd +# on windows +./package/dist/bin/quarto.cmd render tests/docs/playwright/html/tabsets/test.qmd + +# 3. Run playwright from the playwright directory +cd tests/integration/playwright +npx playwright test html-tabsets.spec.ts # Single test +npx playwright test --grep "tabset" # Filter by name +``` + +## CI Execution + +On CI, tests run via the wrapper which handles rendering: + +```bash +./run-tests.sh integration/playwright-tests.test.ts +``` + +The wrapper (`playwright-tests.test.ts`): + +1. Renders all `.qmd` in `docs/playwright/` +2. Installs multiplex server dependencies +3. Runs `npx playwright test` +4. Cleans up rendered output + +## Test Structure + +Tests use `@playwright/test` framework: + +```typescript +import { test, expect } from "@playwright/test"; + +test("Feature description", async ({ page }) => { + await page.goto("/html/feature/test.html"); + + const element = page.getByRole("tab", { name: "Tab 1" }); + await expect(element).toHaveClass(/active/); + await element.click(); + await expect(page.locator("div.content")).toBeVisible(); +}); +``` + +## Configuration + +- **Config file:** `playwright.config.ts` +- **Base URL:** `http://127.0.0.1:8080` +- **Test documents:** `tests/docs/playwright//` +- **Test specs:** `tests/integration/playwright/tests/*.spec.ts` + +## Web Server + +Playwright starts a Python HTTP server automatically when running tests: + +```bash +uv run python -m http.server 8080 +# Serves from tests/docs/playwright/ +``` + +## Utilities + +From `src/utils.ts`: + +| Function | Purpose | +| ---------------------------------- | ------------------------- | +| `getUrl(path)` | Build full URL from path | +| `ojsVal(page, name)` | Get OJS runtime value | +| `ojsRuns(page)` | Wait for OJS to finish | +| `checkColor(element, prop, color)` | Verify CSS color | +| `useDarkLightMode(mode)` | Set color scheme for test | + +## Environment Variables + +| Variable | Effect | +| ------------------------------------------ | ---------------------------------- | +| `QUARTO_PLAYWRIGHT_TESTS_SKIP_RENDER` | Skip rendering (use existing HTML) | +| `QUARTO_PLAYWRIGHT_TESTS_SKIP_CLEANOUTPUT` | Keep rendered files after test | diff --git a/.claude/rules/testing/smoke-all-tests.md b/.claude/rules/testing/smoke-all-tests.md new file mode 100644 index 0000000000..c441436315 --- /dev/null +++ b/.claude/rules/testing/smoke-all-tests.md @@ -0,0 +1,118 @@ +--- +paths: + - "tests/docs/smoke-all/**/*.qmd" + - "tests/docs/smoke-all/**/*.md" + - "tests/docs/smoke-all/**/*.ipynb" +--- + +# Smoke-All Test Format + +Document-based tests using YAML metadata for verification. Tests live in `tests/docs/smoke-all/`. + +## Running Tests + +```bash +# Linux/macOS +./run-tests.sh docs/smoke-all/path/to/test.qmd +./run-tests.sh docs/smoke-all/2023/**/*.qmd # Glob pattern + +# Windows +.\run-tests.ps1 docs/smoke-all/path/to/test.qmd +``` + +## Test Structure + +Tests are defined in `_quarto.tests` YAML metadata: + +```yaml +--- +title: My Test +_quarto: + tests: + html: # Format to test + ensureHtmlElements: # Verification function + - ['div.callout'] # Must exist + - ['div.error', false] # Must NOT exist +--- +``` + +## Available Verification Functions + +See `tests/smoke/smoke-all.test.ts` for the `verifyMap` that defines available functions. + +Common patterns: + +```yaml +_quarto: + tests: + html: + ensureHtmlElements: + - ['div.callout'] # Element must exist + - ['div.error', false] # Element must NOT exist + noErrorsOrWarnings: [] # Clean render (default) + noErrors: [] # Allow warnings + + typst: + ensureTypstFileRegexMatches: # Requires keep-typ: true + - ['#callout\('] + + pdf: + ensureLatexFileRegexMatches: # Requires keep-tex: true + - ['\\begin\{figure\}'] +``` + +### Message Verification + +Lua `warn()` appears as `level: INFO` on TypeScript side: + +```yaml +_quarto: + tests: + html: + printsMessage: + level: INFO + regex: 'WARNING(.*)text' + negate: false # Set true to verify absence +``` + +### Snapshot Testing + +```yaml +_quarto: + tests: + html: + ensureSnapshotMatches: [] +``` + +Save snapshot as `output.html.snapshot` alongside expected output. + +## Execution Control + +```yaml +_quarto: + tests: + run: + skip: true # Skip unconditionally + skip: "Reason for skipping" # Skip with message + ci: false # Skip on CI only + os: darwin # Run only on macOS + os: [windows, darwin] # Run on Windows or macOS + not_os: linux # Don't run on Linux +``` + +Valid OS values: `linux`, `darwin`, `windows` + +## File Organization + +**Issue-based:** `tests/docs/smoke-all/YYYY/MM/DD/.qmd` +**Feature-based:** `tests/docs/smoke-all//` + +## Creating New Tests + +```bash +# Linux/macOS +./new-smoke-all-test.sh 13589 + +# Windows +.\new-smoke-all-test.ps1 13589 +``` diff --git a/.claude/rules/testing/smoke-tests.md b/.claude/rules/testing/smoke-tests.md new file mode 100644 index 0000000000..0ea20050ba --- /dev/null +++ b/.claude/rules/testing/smoke-tests.md @@ -0,0 +1,88 @@ +--- +paths: + - "tests/smoke/**/*.test.ts" +--- + +# Smoke Tests + +TypeScript-based tests that render documents and verify output. Tests live in `tests/smoke/`. + +## Running Tests + +```bash +# Linux/macOS +./run-tests.sh smoke/render/render.test.ts +./run-tests.sh smoke/extensions/ + +# Windows +.\run-tests.ps1 smoke/render/render.test.ts +``` + +## Core Infrastructure + +| File | Purpose | +|------|---------| +| `tests/test.ts` | `testQuartoCmd()`, `testRender()`, test context | +| `tests/verify.ts` | All verification functions | +| `tests/utils.ts` | `docs()`, `outputForInput()`, path utilities | +| `tests/verify-snapshot.ts` | Snapshot comparison utilities | + +## Feature-Specific Utilities + +| File | Purpose | +|------|---------| +| `smoke/render/render.ts` | `testRender()` wrapper, `cleanoutput()` | +| `smoke/site/site.ts` | `testSite()` for website rendering | +| `smoke/website/draft-utils.ts` | Draft post verification helpers | +| `smoke/project/common.ts` | Project testing utilities | +| `smoke/crossref/utils.ts` | Cross-reference verification | +| `smoke/manuscript/manuscript.ts` | Manuscript testing helpers | +| `smoke/jats/render-jats-metadata.ts` | JATS metadata verification | +| `smoke/convert/convert.ts` | Conversion testing utilities | + +## Basic Test Pattern + +```typescript +import { testRender } from "./render.ts"; +import { noErrorsOrWarnings } from "../../verify.ts"; +import { docs } from "../../utils.ts"; + +const inputDir = docs("my-feature/"); + +testRender( + "test.qmd", // Input file + "html", // Format + false, // Keep output? + [noErrorsOrWarnings], // Verifications + { cwd: () => inputDir } +); +``` + +## Test with Setup/Teardown + +```typescript +testRender("test.qmd", "html", false, [noErrorsOrWarnings], { + cwd: () => inputDir, + setup: async () => { + // Create temp files, set up state + }, + teardown: async () => { + // Clean up temp files + }, +}); +``` + +## Test Organization + +``` +tests/smoke/ +├── render/ # General rendering tests +├── crossref/ # Cross-reference tests +├── extensions/ # Extension tests +├── project/ # Project rendering tests +├── site/ # Site/blog tests +├── website/ # Website-specific tests +└── / # Feature-specific tests +``` + +Test fixtures go in `tests/docs//`. diff --git a/.claude/rules/testing/unit-tests.md b/.claude/rules/testing/unit-tests.md new file mode 100644 index 0000000000..32d00996fe --- /dev/null +++ b/.claude/rules/testing/unit-tests.md @@ -0,0 +1,109 @@ +--- +paths: + - "tests/unit/**/*.test.ts" +--- + +# Unit Tests + +TypeScript-based tests for isolated functionality. Tests live in `tests/unit/`. + +## Running Tests + +```bash +# Linux/macOS +./run-tests.sh unit/path.test.ts +./run-tests.sh unit/ # All unit tests + +# Windows +.\run-tests.ps1 unit/path.test.ts +``` + +## Test Structure + +Unit tests use `unitTest()` from `tests/test.ts` with standard assertions: + +```typescript +import { unitTest } from "../test.ts"; +import { assert, assertEquals } from "testing/asserts"; + +// deno-lint-ignore require-await +unitTest("feature - description", async () => { + const result = myFunction(input); + assert(result === expected, "Error message"); +}); +``` + +## Assertions + +From `testing/asserts` (Deno standard library): + +```typescript +import { + assert, // Boolean check + assertEquals, // Deep equality + assertNotEquals, // Deep inequality + assertThrows, // Exception check + assertRejects, // Async exception check +} from "testing/asserts"; +``` + +## Test Organization + +``` +tests/unit/ +├── schema-validation/ # Schema tests +├── mapped-strings/ # String mapping tests +├── yaml-intelligence/ # YAML completion tests +└── .test.ts # Feature-specific tests +``` + +Some directories have their own `utils.ts` for shared helpers. + +## Common Patterns + +### Test with temp files + +```typescript +const workingDir = Deno.makeTempDirSync({ prefix: "quarto-test" }); + +unitTest("feature - with temp files", async () => { + const testFile = join(workingDir, "test.txt"); + Deno.writeTextFileSync(testFile, "content"); + + // Test... + + Deno.removeSync(testFile); +}); +``` + +### Test with fixtures + +```typescript +import { docs } from "../utils.ts"; + +const fixtureDir = docs("my-fixture"); + +unitTest("feature - with fixtures", async () => { + // fixtureDir points to tests/docs/my-fixture/ +}); +``` + +### Async tests + +Most tests are marked `async` but don't actually await: + +```typescript +// deno-lint-ignore require-await +unitTest("sync test", async () => { + // Synchronous test code +}); +``` + +For actual async: + +```typescript +unitTest("async test", async () => { + const result = await asyncFunction(); + assertEquals(result, expected); +}); +``` diff --git a/tests/CLAUDE.md b/tests/CLAUDE.md index 440330fe79..a59f80facb 100644 --- a/tests/CLAUDE.md +++ b/tests/CLAUDE.md @@ -1,157 +1,41 @@ # Test Infrastructure -This directory contains Quarto's test suite. For comprehensive documentation, see `README.md` in this directory. +This directory contains Quarto's test suite. For comprehensive documentation, see `README.md`. -## Quick Reference - -### Running Tests +## Running Tests ```bash cd tests # Linux/macOS -./run-tests.sh # All tests -./run-tests.sh smoke/render/render.test.ts # Specific test file -./run-tests.sh docs/smoke-all/path/to/test.qmd # Smoke-all document +./run-tests.sh # All tests +./run-tests.sh smoke/render/render.test.ts # Specific test +./run-tests.sh docs/smoke-all/path/test.qmd # Smoke-all document # Windows (PowerShell 7+) -.\run-tests.ps1 # All tests -.\run-tests.ps1 smoke/render/render.test.ts # Specific test file -.\run-tests.ps1 docs/smoke-all/path/to/test.qmd # Smoke-all document +.\run-tests.ps1 +.\run-tests.ps1 smoke/render/render.test.ts ``` **Skip dependency configuration:** ```bash -# Linux/macOS -QUARTO_TESTS_NO_CONFIG="true" ./run-tests.sh test.ts - -# Windows -$env:QUARTO_TESTS_NO_CONFIG=$true; .\run-tests.ps1 test.ts -``` - -## Test Structure - -``` -tests/ -├── unit/ # Unit tests -├── integration/ # Integration tests -├── smoke/ # Smoke tests (.test.ts files) -├── docs/ # Test fixtures -│ └── smoke-all/ # Document-based tests (.qmd files) -├── verify.ts # Verification functions -└── utils.ts # Test utilities -``` - -## Smoke-All Test Format - -Document-based tests use `_quarto.tests` YAML metadata: - -```yaml ---- -title: My Test -_quarto: - tests: - html: - ensureHtmlElements: - - ['div.callout'] # Must exist - - ['div.error', false] # Must NOT exist - typst: - ensureTypstFileRegexMatches: - - ['#callout\('] # Pattern must match - - ['ERROR'] # Pattern must NOT match ---- -``` - -**Requires intermediate file preservation:** -- Typst: Add `keep-typ: true` to frontmatter -- LaTeX: Add `keep-tex: true` to frontmatter - -## Key Verification Functions - -From `verify.ts`: - -| Function | Purpose | -|----------|---------| -| `ensureHtmlElements` | Check HTML element presence | -| `ensureTypstFileRegexMatches` | Check Typst source patterns | -| `ensureLatexFileRegexMatches` | Check LaTeX source patterns | -| `ensureFileRegexMatches` | Check any file content | -| `printsMessage` | Verify render output messages | -| `ensureSnapshotMatches` | Compare against saved snapshot | -| `noErrors`, `noErrorsOrWarnings` | Verify clean rendering | - -## Message Verification - -**Important**: Lua `warn()` appears as `level: INFO` on TypeScript side: - -```yaml -_quarto: - tests: - html: - # Verify Lua warning appears - printsMessage: { level: INFO, regex: 'WARNING(.*)text' } - - # Verify message does NOT appear - printsMessage: { level: INFO, regex: 'ERROR', negate: true } -``` - -## Test Control Metadata - -```yaml -_quarto: - tests: - run: - skip: true # Skip unconditionally - skip: "Reason for skipping" # Skip with message - ci: false # Skip on CI only - os: darwin # Run only on macOS - os: [windows, darwin] # Run on Windows or macOS - not_os: linux # Don't run on Linux -``` - -Valid OS values: `linux`, `darwin`, `windows` - -## Test Organization - -**Issue-based tests:** `tests/docs/smoke-all/YYYY/MM/DD/` -- Filename: Issue number (e.g., `13589.qmd`) -- Multiple related: `13589.qmd`, `13589-valid.qmd` -- Reference issue in title: `"Bug with feature (#13589)"` - -**Feature tests:** `tests/docs/smoke-all//` -- Examples: `typst/`, `crossrefs/`, `html/` - -## Common Patterns - -### Regex Best Practices - -**Be specific to avoid template matches:** -```yaml -# Bad - matches template boilerplate -- ['#figure\('] - -# Good - matches specific document structure -- ['#figure\(\[(\r\n?|\n)#block\[(\r\n?|\n)#callout'] +QUARTO_TESTS_NO_CONFIG="true" ./run-tests.sh test.ts # Linux/macOS +$env:QUARTO_TESTS_NO_CONFIG=$true; .\run-tests.ps1 # Windows ``` -**Line breaks:** -- `\s*` - Zero or more whitespace (spaces, tabs, newlines) -- `(\r\n?|\n)` - Only line breaks (Windows or Unix) +## Test Types -### Snapshot Testing - -```yaml -_quarto: - tests: - html: - ensureSnapshotMatches: [] -``` - -Save snapshot file as `output.html.snapshot` alongside expected output. +| Type | Location | File Pattern | Details | +|------|----------|--------------|---------| +| Unit | `tests/unit/` | `*.test.ts` | `.claude/rules/testing/unit-tests.md` | +| Smoke | `tests/smoke/` | `*.test.ts` | `.claude/rules/testing/smoke-tests.md` | +| Smoke-all | `tests/docs/smoke-all/` | `*.qmd` | `.claude/rules/testing/smoke-all-tests.md` | +| Playwright | `tests/integration/playwright/` | `*.spec.ts` | `.claude/rules/testing/playwright-tests.md` | ## Dependencies -Tests require R, Python, and Julia. Run configuration script: +Tests require R, Python, and Julia. Run configuration script to set up: + ```bash # Linux/macOS ./configure-test-env.sh @@ -160,17 +44,27 @@ Tests require R, Python, and Julia. Run configuration script: .\configure-test-env.ps1 ``` -Or set `QUARTO_TESTS_NO_CONFIG=true` to skip. +Managed via: +- **R**: renv (`renv.lock`) +- **Python**: uv (`pyproject.toml`, `uv.lock`) +- **Julia**: Pkg.jl (`Project.toml`, `Manifest.toml`) + +## Core Files + +| File | Purpose | +|------|---------| +| `test.ts` | Test infrastructure (`testQuartoCmd`, `unitTest`) | +| `verify.ts` | Verification functions | +| `utils.ts` | Path utilities (`docs()`, `outputForInput()`) | +| `README.md` | Comprehensive documentation | -## Debugging Tests +## Debugging -1. **Run single test** to isolate failures -2. **Check render output** - tests capture stdout/stderr -3. **Keep intermediate files** - Add `keep-typ: true` or `keep-tex: true` -4. **VSCode debugging** - See `.vscode/launch.json` configuration +1. Run single test to isolate failures +2. Check render output - tests capture stdout/stderr +3. VSCode debugging via `.vscode/launch.json` ## Related Documentation - **Full test docs**: `tests/README.md` - **Flaky test debugging**: https://gist.github.com/cderv/77405f5a5ea0c1db38693159c4a260dd -- **Rule file**: `.claude/rules/testing/smoke-all-format.md` From db0de1a3df3429a8bfde5a4d708658b0382013b3 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 22 Jan 2026 10:36:50 +0100 Subject: [PATCH 03/16] Consolidate Lua filter documentation to avoid duplication - Slim down src/resources/filters/CLAUDE.md to unique content only - Remove coding patterns now covered by rules file - Add API Reference section to rules file pointing to lua-types/ Co-Authored-By: Claude Opus 4.5 --- .claude/rules/filters/lua-development.md | 9 ++ src/resources/filters/CLAUDE.md | 125 +++-------------------- 2 files changed, 22 insertions(+), 112 deletions(-) diff --git a/.claude/rules/filters/lua-development.md b/.claude/rules/filters/lua-development.md index d8e0a0c7c6..fdc9659e09 100644 --- a/.claude/rules/filters/lua-development.md +++ b/.claude/rules/filters/lua-development.md @@ -223,6 +223,15 @@ When adding filters to `main.lua`: -- Filters in same stage run in order defined ``` +## API Reference + +Consult `src/resources/lua-types/` for available methods, properties, and function signatures: + +- `lua-types/pandoc/` - Pandoc Lua API (blocks, inlines, List, utils, etc.) +- `lua-types/quarto/` - Quarto Lua API (format detection, custom nodes, etc.) + +These type definition files document the complete API surface. + ## Key Conventions Summary 1. **Use `import()` for filters** - `require()` for modules only diff --git a/src/resources/filters/CLAUDE.md b/src/resources/filters/CLAUDE.md index 6aaf19009e..146841534b 100644 --- a/src/resources/filters/CLAUDE.md +++ b/src/resources/filters/CLAUDE.md @@ -2,16 +2,7 @@ This directory contains Quarto's Lua filter system - a multi-stage document transformation pipeline that processes Pandoc AST through ~212 Lua files. -## Quick Reference - -| Task | Location | -|------|----------| -| Add new filter | Edit `main.lua`, add `import()` + filter entry | -| Create custom node | `customnodes/` + `_quarto.ast.add_handler()` | -| Check format | `_quarto.format.isHtmlOutput()`, `.isLatexOutput()`, etc. | -| Logging | `warn()`, `error()`, `info()` from `common/log.lua` | -| Debug dump | `quarto.utils.dump(obj)` | -| Trace filters | `quarto dev-call show-ast-trace document.qmd` | +**For coding conventions:** See `.claude/rules/filters/lua-development.md` ## Directory Structure @@ -50,85 +41,19 @@ filters/ 7. FINALIZE (quarto-finalize/) - cleanup, dependencies ``` -User filters can run between stages via entry points (`pre-ast`, `post-ast`, `pre-quarto`, etc.). - -## Custom Node System - -Quarto extends Pandoc AST with custom node types (callouts, tabsets, floats, etc.). - -**Lifecycle:** -1. **Parse** (quarto-pre): Detect special Div → create custom node -2. **Transform**: Filters manipulate custom nodes -3. **Render** (quarto-post): Convert back to Pandoc AST for output - -**Example custom node handler:** -```lua -_quarto.ast.add_handler({ - class_name = "callout", - ast_name = "Callout", - kind = "Block", - parse = function(div) ... end, - slots = { "title", "content" }, - constructor = function(tbl) ... end -}) -``` - -**Key files:** -- `ast/customnodes.lua` - Core system -- `customnodes/*.lua` - Individual implementations - -## Common Patterns - -**Format-specific rendering:** -```lua -if _quarto.format.isLatexOutput() then - return pandoc.RawBlock("latex", "\\begin{...}...") -elseif _quarto.format.isHtmlOutput() then - return pandoc.Div(...) -end -``` - -**Walking custom nodes:** -```lua -doc = _quarto.ast.walk(doc, filter) -- Handles custom nodes -``` - -**Checking node types:** -```lua -if is_custom_node(node, "Callout") then ... end -if is_regular_node(node, "Div") then ... end -- NOT custom node -``` - -**Reading options:** -```lua -local value = option("callout-icon", true) -local engine = param("execution-engine") -``` +User filters run between stages via entry points (`pre-ast`, `post-ast`, `pre-quarto`, etc.). -## Adding a New Filter +## Key Files -1. Choose stage: `quarto-pre/`, `quarto-post/`, etc. -2. Create filter file: - ```lua - -- quarto-pre/my-feature.lua - function my_feature() - return { - Div = function(div) - if div.classes:includes("my-feature") then - return process(div) - end - end - } - end - ``` -3. Import in `main.lua`: - ```lua - import("./quarto-pre/my-feature.lua") - ``` -4. Add to filter list in `main.lua`: - ```lua - { name = "pre-my-feature", filter = my_feature() } - ``` +| File | Purpose | +|------|---------| +| `main.lua` | Filter chain definition (~725 lines) | +| `mainstateinit.lua` | Global state initialization | +| `ast/customnodes.lua` | Custom node system | +| `common/log.lua` | Logging utilities | +| `common/debug.lua` | Debug utilities (`dump`, `tdump`) | +| `common/format.lua` | Format detection | +| `common/options.lua` | Metadata option reading | ## Debugging @@ -141,37 +66,13 @@ package/dist/bin/quarto dev-call show-ast-trace document.qmd package/dist/bin/quarto.cmd dev-call show-ast-trace document.qmd ``` -**Print debugging:** -```lua -quarto.utils.dump(node) -- Pretty-print any object -warn("Debug: " .. tostring(value)) -- Appears in console -``` - **AST diagram:** ```bash quarto dev-call make-ast-diagram document.qmd ``` -## Common Gotchas - -1. **Custom vs regular nodes**: Use `is_regular_node()` to exclude custom nodes -2. **Slot assignment**: Use proxy pattern, don't assign directly to `.content` -3. **Filter returns**: Return `nil` to continue, return value to replace -4. **Lua messages**: `warn()` appears as INFO level on TypeScript side - -## Key Files - -| File | Purpose | -|------|---------| -| `main.lua` | Filter chain definition (~725 lines) | -| `common/log.lua` | Logging utilities | -| `common/debug.lua` | Debug utilities (`dump`, `tdump`) | -| `common/format.lua` | Format detection | -| `common/options.lua` | Metadata option reading | -| `ast/customnodes.lua` | Custom node system | - ## Related Documentation +- **Coding conventions**: `.claude/rules/filters/lua-development.md` - **Lua API**: - **Filter tracing**: `dev-docs/lua-filter-trace-viewer.qmd` -- **Rule file**: `.claude/rules/filters/lua-development.md` From 28905bf42b46bdff94bcec7b42be5ef9dcb796e7 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Tue, 3 Feb 2026 15:16:11 +0100 Subject: [PATCH 04/16] Add YAML regex escaping guidance to test documentation Document the common mistake of over-escaping regex patterns in YAML single-quoted strings (e.g., '\(' vs '\(') which causes test failures. - Add escaping cheat sheet to smoke-all-tests.md rule file - Add paths to trigger rule when reading verify.ts or smoke-all.test.ts - Bring testing-patterns.md from main and add smoke-all section Co-Authored-By: Claude Opus 4.5 --- .claude/rules/testing/smoke-all-tests.md | 32 ++++++ llm-docs/testing-patterns.md | 127 ++++++----------------- 2 files changed, 66 insertions(+), 93 deletions(-) diff --git a/.claude/rules/testing/smoke-all-tests.md b/.claude/rules/testing/smoke-all-tests.md index c441436315..c568149b74 100644 --- a/.claude/rules/testing/smoke-all-tests.md +++ b/.claude/rules/testing/smoke-all-tests.md @@ -3,6 +3,8 @@ paths: - "tests/docs/smoke-all/**/*.qmd" - "tests/docs/smoke-all/**/*.md" - "tests/docs/smoke-all/**/*.ipynb" + - "tests/smoke/smoke-all.test.ts" + - "tests/verify.ts" --- # Smoke-All Test Format @@ -102,6 +104,36 @@ _quarto: Valid OS values: `linux`, `darwin`, `windows` +## YAML String Escaping for Regex + +**Critical rule:** In YAML single-quoted strings, `'\('` and `"\\("` are equivalent - both produce a literal `\(` in the regex. + +**Common mistake:** Over-escaping with `'\\('` produces `\\(` (two backslashes), causing regex to fail. + +```yaml +_quarto: + tests: + latex: + ensureFileRegexMatches: + # CORRECT - single backslash in YAML single quotes + - ['\(1\)', '\\circled\{1\}', "Variable assignment"] + - ['\\CommentTok', '\\begin\{Shaded\}'] + + # WRONG - over-escaped (produces \\( in regex) + - ['\\(1\\)', '\\\\circled\\{1\\}'] +``` + +**YAML escaping cheat sheet:** + +| To match in file | In single quotes `'...'` | In double quotes `"..."` | +|------------------|--------------------------|--------------------------| +| `\(` | `'\('` | `"\\("` | +| `\begin{` | `'\\begin\{'` | `"\\\\begin\\{"` | +| `\\` (literal) | `'\\\\'` | `"\\\\\\\\"` | +| `[` (regex) | `'\['` | `"\\["` | + +**Recommendation:** Use single-quoted strings. They're simpler - only `'` itself needs escaping (as `''`). + ## File Organization **Issue-based:** `tests/docs/smoke-all/YYYY/MM/DD/.qmd` diff --git a/llm-docs/testing-patterns.md b/llm-docs/testing-patterns.md index 3d0c65a084..426ca2ce0a 100644 --- a/llm-docs/testing-patterns.md +++ b/llm-docs/testing-patterns.md @@ -88,62 +88,6 @@ testQuartoCmd( - Use absolute paths with `join()` for file verification - Clean up output directories in teardown -### Pre/Post Render Script Tests - -For testing pre-render or post-render scripts that run during project rendering: - -```typescript -import { docs } from "../../utils.ts"; -import { join } from "../../../src/deno_ral/path.ts"; -import { existsSync } from "../../../src/deno_ral/fs.ts"; -import { testQuartoCmd } from "../../test.ts"; -import { noErrors, validJsonWithFields } from "../../verify.ts"; -import { safeRemoveIfExists } from "../../../src/core/path.ts"; - -const projectDir = docs("project/prepost/my-test"); -const projectDirAbs = join(Deno.cwd(), projectDir); -const dumpPath = join(projectDirAbs, "output.json"); -const outDir = join(projectDirAbs, "_site"); - -testQuartoCmd( - "render", - [projectDir], - [ - noErrors, - validJsonWithFields(dumpPath, { - expected: "value", - }), - ], - { - teardown: async () => { - safeRemoveIfExists(dumpPath); - if (existsSync(outDir)) { - await Deno.remove(outDir, { recursive: true }); - } - }, - }, -); -``` - -**Fixture structure:** - -``` -tests/docs/project/prepost/my-test/ -├── _quarto.yml # project config with pre-render/post-render scripts -├── index.qmd # minimal page (website needs at least one) -├── check-env.ts # pre/post-render script (Deno TypeScript) -└── .gitignore # exclude .quarto/ and *.quarto_ipynb -``` - -**Key points:** -- Pre/post-render scripts run as subprocesses with `cwd` set to the project directory -- Scripts access environment variables via `Deno.env.get()` and can write files for verification -- Use `validJsonWithFields` for JSON file verification (parses and compares field values exactly) -- Use `ensureFileRegexMatches` for non-JSON files or when regex matching is needed -- The file dump pattern (script writes JSON, test reads it) is useful for verifying env vars and other runtime state -- Clean up both the dump file and the output directory in teardown -- Existing fixtures: `tests/docs/project/prepost/` (mutate-render-list, invalid-mutate, extension, issue-10828, script-env-vars) - ### Extension Template Tests For testing `quarto use template`: @@ -220,43 +164,6 @@ folderExists(path: string) directoryEmptyButFor(dir: string, allowedFiles: string[]) ``` -### Content Verifiers - -```typescript -// Regex match on file contents (matches required, noMatches must be absent) -ensureFileRegexMatches(file: string, matches: (string | RegExp)[], noMatches?: (string | RegExp)[]) - -// Regex match on CSS files linked from HTML -ensureCssRegexMatches(file: string, matches: (string | RegExp)[], noMatches?: (string | RegExp)[]) - -// Check HTML elements exist or don't exist (CSS selectors) -ensureHtmlElements(file: string, noElements: string[], elements: string[]) - -// Verify JSON structure has expected fields (parses JSON, compares values with deep equality) -validJsonWithFields(file: string, fields: Record) - -// Check output message at specific log level -printsMessage(options: { level: string, regex: RegExp }) -``` - -### Assertion Helpers - -```typescript -// Assert path exists (throws if missing) -verifyPath(path: string) - -// Assert path does NOT exist (throws if present) -verifyNoPath(path: string) -``` - -### Cleanup Helpers - -```typescript -// Safe file removal (no error if missing) - from src/core/path.ts -import { safeRemoveIfExists } from "../../../src/core/path.ts"; -safeRemoveIfExists(path: string) -``` - ### Path Helpers ```typescript @@ -451,3 +358,37 @@ testQuartoCmd( ``` Run test **without fix** first to verify it fails, then verify it passes with fix. + +## Smoke-All Tests (YAML-Based) + +Smoke-all tests embed test specifications directly in `.qmd` files using `_quarto.tests` metadata. See `.claude/rules/testing/smoke-all-tests.md` for full documentation. + +### YAML String Escaping for Regex + +**Critical rule:** In YAML single-quoted strings, `'\('` and `"\\("` are equivalent - both produce a literal `\(` in the regex. + +**Common mistake:** Over-escaping with `'\\('` produces `\\(` (two backslashes), causing regex to fail. + +```yaml +_quarto: + tests: + pdf: + ensureLatexFileRegexMatches: + # CORRECT - single backslash in YAML single quotes + - ['\(1\)', '\\circled\{1\}', "Variable assignment"] + - ['\\CommentTok', '\\begin\{Shaded\}'] + + # WRONG - over-escaped (produces \\( in regex) + - ['\\(1\\)', '\\\\circled\\{1\\}'] +``` + +**YAML escaping cheat sheet:** + +| To match in file | In single quotes `'...'` | In double quotes `"..."` | +|------------------|--------------------------|--------------------------| +| `\(` | `'\('` | `"\\("` | +| `\begin{` | `'\\begin\{'` | `"\\\\begin\\{"` | +| `\\` (literal) | `'\\\\'` | `"\\\\\\\\"` | +| `[` (regex) | `'\['` | `"\\["` | + +**Recommendation:** Use single-quoted strings. They're simpler - only `'` itself needs escaping (as `''`). From 34820a09e01d5c86770a1de05b1363e31867f6b7 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 5 Feb 2026 15:29:40 +0100 Subject: [PATCH 05/16] Add notes about what shouldn't be done in tests --- .claude/rules/testing/test-anti-patterns.md | 19 ++++++ llm-docs/testing-patterns.md | 69 +++++++++++++++------ 2 files changed, 69 insertions(+), 19 deletions(-) create mode 100644 .claude/rules/testing/test-anti-patterns.md diff --git a/.claude/rules/testing/test-anti-patterns.md b/.claude/rules/testing/test-anti-patterns.md new file mode 100644 index 0000000000..ab18275a71 --- /dev/null +++ b/.claude/rules/testing/test-anti-patterns.md @@ -0,0 +1,19 @@ +--- +paths: + - "tests/**/*.ts" + - "tests/**/*.test.ts" +--- + +# Test Anti-Patterns + +## Don't: Modify Environment Variables + +`Deno.env.set()` modifies process-global state. Deno runs test files in parallel by default, so other tests can see modified values. + +**Details:** `llm-docs/testing-patterns.md` → "Environment Variable Testing Pitfalls" + +## Don't: Create Language Environments in Test Subdirectories + +Never create `Project.toml`, `.venv/`, or `renv.lock` in test fixture directories. + +**Details:** `llm-docs/testing-patterns.md` → "Shared Test Environments" diff --git a/llm-docs/testing-patterns.md b/llm-docs/testing-patterns.md index 426ca2ce0a..66661c2340 100644 --- a/llm-docs/testing-patterns.md +++ b/llm-docs/testing-patterns.md @@ -5,6 +5,7 @@ This document describes the standard patterns for writing smoke tests in the Qua ## Test Structure Overview Quarto uses Deno for testing with custom verification helpers located in: + - `tests/test.ts` - Core test runner (`testQuartoCmd`) - `tests/verify.ts` - Verification helpers (`fileExists`, `pathDoNotExists`, etc.) - `tests/utils.ts` - Utility functions (`docs()`, `outputForInput()`, etc.) @@ -34,13 +35,12 @@ const input = docs("minimal.qmd"); const output = outputForInput(input, "html"); testRender(input, "html", true, [ - ensureHtmlElements(output.outputPath, [], [ - "script#quarto-html-after-body", - ]), + ensureHtmlElements(output.outputPath, [], ["script#quarto-html-after-body"]), ]); ``` **Key points:** + - `testRender()` automatically handles output verification and cleanup - Respects `QUARTO_TEST_KEEP_OUTPUTS` env var for debugging - Set `noSupporting` parameter based on expected output: @@ -61,15 +61,15 @@ import { existsSync } from "../../../src/deno_ral/fs.ts"; import { testQuartoCmd } from "../../test.ts"; import { fileExists, pathDoNotExists, noErrors } from "../../verify.ts"; -const projectDir = docs("project/my-test"); // Relative path via docs() -const outputDir = join(projectDir, "_site"); // Append output dir for websites +const projectDir = docs("project/my-test"); // Relative path via docs() +const outputDir = join(projectDir, "_site"); // Append output dir for websites testQuartoCmd( "render", [projectDir], [ - noErrors, // Check for no errors - fileExists(join(outputDir, "index.html")), // Verify expected file exists + noErrors, // Check for no errors + fileExists(join(outputDir, "index.html")), // Verify expected file exists pathDoNotExists(join(outputDir, "ignored.html")), // Verify file doesn't exist ], { @@ -83,6 +83,7 @@ testQuartoCmd( ``` **Key points:** + - Use `docs()` helper to create relative paths from `tests/docs/` - For website projects, output goes to `_site` subdirectory - Use absolute paths with `join()` for file verification @@ -94,7 +95,11 @@ For testing `quarto use template`: ```typescript import { testQuartoCmd } from "../../test.ts"; -import { fileExists, noErrorsOrWarnings, pathDoNotExists } from "../../verify.ts"; +import { + fileExists, + noErrorsOrWarnings, + pathDoNotExists, +} from "../../verify.ts"; import { join } from "../../../src/deno_ral/path.ts"; import { ensureDirSync } from "../../../src/deno_ral/fs.ts"; @@ -115,11 +120,11 @@ testQuartoCmd( ["template", templateSourceDir, "--no-prompt"], [ noErrorsOrWarnings, - fileExists(`${templateFolder}.qmd`), // Relative - template file renamed to folder name + fileExists(`${templateFolder}.qmd`), // Relative - template file renamed to folder name pathDoNotExists(join(workingDir, "README.md")), // Absolute - excluded file ], { - cwd: () => workingDir, // Set working directory + cwd: () => workingDir, // Set working directory teardown: () => { try { Deno.removeSync(tempDir, { recursive: true }); @@ -127,12 +132,13 @@ testQuartoCmd( // Ignore cleanup errors } return Promise.resolve(); - } - } + }, + }, ); ``` **Key points:** + - Use `Deno.makeTempDirSync()` for isolated test environment - Create mock template source with test files - Template files get renamed to match target directory name @@ -234,7 +240,7 @@ teardown: async () => { if (existsSync(outputPath)) { await Deno.remove(outputPath, { recursive: true }); } -} +}; ``` ### Multiple Test Cases @@ -266,17 +272,21 @@ testQuartoCmd(...); ## Examples from Codebase ### Simple Render Test + See `tests/smoke/render/render-plain.test.ts` for the simplest render tests (no additional verifiers). See `tests/smoke/render/render-minimal.test.ts` for render test with custom HTML element verification. ### Project Ignore Test + See `tests/smoke/project/project-ignore-dirs.test.ts` for testing directory exclusion patterns. ### Website Rendering Test + See `tests/smoke/project/project-website.test.ts` for website project rendering patterns. ### Template Usage Test + See `tests/smoke/use/template.test.ts` for extension template patterns. ## Engine-Specific Test Considerations @@ -302,6 +312,7 @@ tests/docs/my-test/ ``` **Why this fails:** + - Julia searches UP for `Project.toml` and uses the first one found - Python/R will use local environments if present - CI scripts won't configure these local environments @@ -339,6 +350,26 @@ Rscript -e "renv::install(); renv::snapshot()" 6. **Comment intent**: Add comments explaining what should/shouldn't happen 7. **Handle errors**: Wrap cleanup in try-catch to avoid test suite failures from cleanup issues +## Environment Variable Testing Pitfalls + +`Deno.env.set()` modifies process-global state. Deno runs test files in parallel by default (same OS process), so concurrent tests can see modified values. Save/restore patterns don't help - other tests see the modified value during the test window. + +| Execution Mode | Risk | Why | +| -------------------------- | ------------------ | --------------------------------------- | +| `./run-tests.sh` (default) | **Race condition** | Files run in parallel, share `Deno.env` | +| `./run-parallel-tests.sh` | **None** | Separate OS processes | + +**Existing bad pattern** - `tests/smoke/website/drafts-env.test.ts`: + +```typescript +// BAD: Sets env var, never restores it +// Only "works" because no other test reads QUARTO_PROFILE +Deno.env.set("QUARTO_PROFILE", "drafts"); +testQuartoCmd("render", [renderDir], [...]); +``` + +**Alternatives:** Unit test the env var reader, refactor code to accept parameters, or use subprocess isolation. + ## Testing File Exclusion When testing that files are excluded (like AI config files): @@ -350,7 +381,7 @@ testQuartoCmd( [projectDir], [ noErrors, - fileExists(join(outputDir, "expected.html")), // Should exist + fileExists(join(outputDir, "expected.html")), // Should exist pathDoNotExists(join(outputDir, "excluded.html")), // Should NOT exist ], // ... @@ -385,10 +416,10 @@ _quarto: **YAML escaping cheat sheet:** | To match in file | In single quotes `'...'` | In double quotes `"..."` | -|------------------|--------------------------|--------------------------| -| `\(` | `'\('` | `"\\("` | -| `\begin{` | `'\\begin\{'` | `"\\\\begin\\{"` | -| `\\` (literal) | `'\\\\'` | `"\\\\\\\\"` | -| `[` (regex) | `'\['` | `"\\["` | +| ---------------- | ------------------------ | ------------------------ | +| `\(` | `'\('` | `"\\("` | +| `\begin{` | `'\\begin\{'` | `"\\\\begin\\{"` | +| `\\` (literal) | `'\\\\'` | `"\\\\\\\\"` | +| `[` (regex) | `'\['` | `"\\["` | **Recommendation:** Use single-quoted strings. They're simpler - only `'` itself needs escaping (as `''`). From 041ff87036e297e323a7f41929bd47bfb021c7e3 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 5 Feb 2026 16:37:40 +0100 Subject: [PATCH 06/16] Reduce context token usage in Claude Code rule files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split, merge, and trim rule files to reduce context tokens while maintaining coverage. Key changes: Split deno-conventions.md (212 lines) into targeted files: - deno-essentials.md (82 lines) for src/**/*.ts - cliffy-commands.md (48 lines) for src/command/**/*.ts - deno-ral.md (58 lines) for src/deno_ral/**/*.ts Merge smoke-tests.md + unit-tests.md (197 lines) into: - typescript-tests.md (94 lines) covering both Remove duplications: - YAML regex escaping: keep in llm-docs, add pointer in smoke-all-tests.md - --output-dir detail: already in render-pipeline.md Trim verbose sections: - format-handlers.md: remove Common Patterns (covered in Adding a New Format) Results: - Any .ts file: 212 → 82 lines loaded - src/command/: 212 → 130 lines loaded - Net reduction: ~210 lines across all rules Co-Authored-By: Claude Opus 4.5 --- .claude/CLAUDE.md | 15 -- .claude/rules/formats/format-handlers.md | 42 ---- .claude/rules/testing/smoke-all-tests.md | 28 +-- .claude/rules/testing/smoke-tests.md | 88 -------- .claude/rules/testing/typescript-tests.md | 94 ++++++++ .claude/rules/testing/unit-tests.md | 109 ---------- .claude/rules/typescript/cliffy-commands.md | 48 +++++ .claude/rules/typescript/deno-conventions.md | 212 ------------------- .claude/rules/typescript/deno-essentials.md | 82 +++++++ .claude/rules/typescript/deno-ral.md | 58 +++++ 10 files changed, 284 insertions(+), 492 deletions(-) delete mode 100644 .claude/rules/testing/smoke-tests.md create mode 100644 .claude/rules/testing/typescript-tests.md delete mode 100644 .claude/rules/testing/unit-tests.md create mode 100644 .claude/rules/typescript/cliffy-commands.md delete mode 100644 .claude/rules/typescript/deno-conventions.md create mode 100644 .claude/rules/typescript/deno-essentials.md create mode 100644 .claude/rules/typescript/deno-ral.md diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 3d8acc7892..d72474fe29 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -128,21 +128,6 @@ The feature format matrix in `dev-docs/feature-format-matrix/` documents and tes - Project types are registered via `project/types/register.ts` - Each type defines metadata, rendering behavior, and output structure -**Single-File Rendering with `--output-dir` (Non-obvious variant)** - -When `--output-dir` is used without a project file, Quarto creates a "synthetic" project context: - -- Triggers when: `quarto render file.qmd --output-dir output/` and no `_quarto.yml` exists -- Creates temporary `.quarto` directory to manage the render -- Uses full `renderProject()` path, NOT `singleFileProjectContext()` -- `forceClean` flag in `RenderOptions` signals this is temporary and needs cleanup -- After rendering: closes file handles then removes `.quarto` directory -- Key locations: - - Creation: [render-shared.ts:52-58](src/command/render/render-shared.ts#L52-L58) - - Cleanup: [project.ts:889-907](src/command/render/project.ts#L889-L907) -- Related issues: #9745 (avoid leaving `.quarto` debris), #13625 (Windows file locking) -- Test coverage: [tests/smoke/render/render-output-dir.test.ts](tests/smoke/render/render-output-dir.test.ts) - **Format System** (`src/format/`) - Format handlers for different output types (HTML, PDF, DOCX, reveal.js, etc.) diff --git a/.claude/rules/formats/format-handlers.md b/.claude/rules/formats/format-handlers.md index 6ad029730f..f6a855bf5e 100644 --- a/.claude/rules/formats/format-handlers.md +++ b/.claude/rules/formats/format-handlers.md @@ -193,48 +193,6 @@ format: myformat Test content ``` -## Common Patterns - -**Format extending another:** -```typescript -// Dashboard extends HTML -const dashboardFormat = mergeConfigs( - htmlFormat(7, 5), - { - identifier: { displayName: "Dashboard", ... }, - render: { echo: false }, - // Override formatExtras to add dashboard processing - }, -); -``` - -**Wrapping formatExtras:** -```typescript -const baseExtras = baseFormat.formatExtras; -return { - ...baseFormat, - formatExtras: async (...args) => { - const extras = baseExtras ? await baseExtras(...args) : {}; - return { - ...extras, - postprocessors: [ - ...(extras.postprocessors || []), - myPostprocessor, - ], - }; - }, -}; -``` - -**Binary output (PDF):** -```typescript -const pdfFormat = createFormat("PDF", "pdf", { - pandoc: { to: "latex" }, // Pandoc produces LaTeX - render: { "keep-tex": false }, - // postprocessors convert .tex → .pdf via latexmk -}); -``` - ## Directory Structure ``` diff --git a/.claude/rules/testing/smoke-all-tests.md b/.claude/rules/testing/smoke-all-tests.md index c568149b74..acbe037e0a 100644 --- a/.claude/rules/testing/smoke-all-tests.md +++ b/.claude/rules/testing/smoke-all-tests.md @@ -106,33 +106,9 @@ Valid OS values: `linux`, `darwin`, `windows` ## YAML String Escaping for Regex -**Critical rule:** In YAML single-quoted strings, `'\('` and `"\\("` are equivalent - both produce a literal `\(` in the regex. +**Details:** `llm-docs/testing-patterns.md` → "YAML String Escaping for Regex" -**Common mistake:** Over-escaping with `'\\('` produces `\\(` (two backslashes), causing regex to fail. - -```yaml -_quarto: - tests: - latex: - ensureFileRegexMatches: - # CORRECT - single backslash in YAML single quotes - - ['\(1\)', '\\circled\{1\}', "Variable assignment"] - - ['\\CommentTok', '\\begin\{Shaded\}'] - - # WRONG - over-escaped (produces \\( in regex) - - ['\\(1\\)', '\\\\circled\\{1\\}'] -``` - -**YAML escaping cheat sheet:** - -| To match in file | In single quotes `'...'` | In double quotes `"..."` | -|------------------|--------------------------|--------------------------| -| `\(` | `'\('` | `"\\("` | -| `\begin{` | `'\\begin\{'` | `"\\\\begin\\{"` | -| `\\` (literal) | `'\\\\'` | `"\\\\\\\\"` | -| `[` (regex) | `'\['` | `"\\["` | - -**Recommendation:** Use single-quoted strings. They're simpler - only `'` itself needs escaping (as `''`). +**Quick rule:** In YAML single quotes, use single backslash: `'\('` matches `\(`. Double-escaping `'\\('` is wrong. ## File Organization diff --git a/.claude/rules/testing/smoke-tests.md b/.claude/rules/testing/smoke-tests.md deleted file mode 100644 index 0ea20050ba..0000000000 --- a/.claude/rules/testing/smoke-tests.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -paths: - - "tests/smoke/**/*.test.ts" ---- - -# Smoke Tests - -TypeScript-based tests that render documents and verify output. Tests live in `tests/smoke/`. - -## Running Tests - -```bash -# Linux/macOS -./run-tests.sh smoke/render/render.test.ts -./run-tests.sh smoke/extensions/ - -# Windows -.\run-tests.ps1 smoke/render/render.test.ts -``` - -## Core Infrastructure - -| File | Purpose | -|------|---------| -| `tests/test.ts` | `testQuartoCmd()`, `testRender()`, test context | -| `tests/verify.ts` | All verification functions | -| `tests/utils.ts` | `docs()`, `outputForInput()`, path utilities | -| `tests/verify-snapshot.ts` | Snapshot comparison utilities | - -## Feature-Specific Utilities - -| File | Purpose | -|------|---------| -| `smoke/render/render.ts` | `testRender()` wrapper, `cleanoutput()` | -| `smoke/site/site.ts` | `testSite()` for website rendering | -| `smoke/website/draft-utils.ts` | Draft post verification helpers | -| `smoke/project/common.ts` | Project testing utilities | -| `smoke/crossref/utils.ts` | Cross-reference verification | -| `smoke/manuscript/manuscript.ts` | Manuscript testing helpers | -| `smoke/jats/render-jats-metadata.ts` | JATS metadata verification | -| `smoke/convert/convert.ts` | Conversion testing utilities | - -## Basic Test Pattern - -```typescript -import { testRender } from "./render.ts"; -import { noErrorsOrWarnings } from "../../verify.ts"; -import { docs } from "../../utils.ts"; - -const inputDir = docs("my-feature/"); - -testRender( - "test.qmd", // Input file - "html", // Format - false, // Keep output? - [noErrorsOrWarnings], // Verifications - { cwd: () => inputDir } -); -``` - -## Test with Setup/Teardown - -```typescript -testRender("test.qmd", "html", false, [noErrorsOrWarnings], { - cwd: () => inputDir, - setup: async () => { - // Create temp files, set up state - }, - teardown: async () => { - // Clean up temp files - }, -}); -``` - -## Test Organization - -``` -tests/smoke/ -├── render/ # General rendering tests -├── crossref/ # Cross-reference tests -├── extensions/ # Extension tests -├── project/ # Project rendering tests -├── site/ # Site/blog tests -├── website/ # Website-specific tests -└── / # Feature-specific tests -``` - -Test fixtures go in `tests/docs//`. diff --git a/.claude/rules/testing/typescript-tests.md b/.claude/rules/testing/typescript-tests.md new file mode 100644 index 0000000000..42870a00e7 --- /dev/null +++ b/.claude/rules/testing/typescript-tests.md @@ -0,0 +1,94 @@ +--- +paths: + - "tests/smoke/**/*.test.ts" + - "tests/unit/**/*.test.ts" +--- + +# TypeScript Tests + +TypeScript-based tests using Deno. Smoke tests render documents; unit tests verify isolated functionality. + +## Running Tests + +```bash +# Linux/macOS +./run-tests.sh smoke/render/render.test.ts # Specific smoke test +./run-tests.sh unit/path.test.ts # Specific unit test +./run-tests.sh smoke/extensions/ # Directory + +# Windows +.\run-tests.ps1 smoke/render/render.test.ts +``` + +## Core Infrastructure + +| File | Purpose | +|------|---------| +| `tests/test.ts` | `testQuartoCmd()`, `testRender()`, `unitTest()` | +| `tests/verify.ts` | Verification functions (`noErrors`, `fileExists`, etc.) | +| `tests/utils.ts` | `docs()`, `outputForInput()`, path utilities | + +## Smoke Tests (`tests/smoke/`) + +Render documents and verify output: + +```typescript +import { testRender } from "./render.ts"; +import { noErrorsOrWarnings } from "../../verify.ts"; +import { docs } from "../../utils.ts"; + +testRender( + "test.qmd", + "html", + false, // Keep output? + [noErrorsOrWarnings], + { cwd: () => docs("my-feature/") } +); +``` + +With setup/teardown: +```typescript +testRender("test.qmd", "html", false, [noErrorsOrWarnings], { + cwd: () => inputDir, + setup: async () => { /* Create temp files */ }, + teardown: async () => { /* Cleanup */ }, +}); +``` + +## Unit Tests (`tests/unit/`) + +Test isolated functionality: + +```typescript +import { unitTest } from "../test.ts"; +import { assert, assertEquals } from "testing/asserts"; + +// deno-lint-ignore require-await +unitTest("feature - description", async () => { + const result = myFunction(input); + assertEquals(result, expected); +}); +``` + +Assertions from `testing/asserts`: `assert`, `assertEquals`, `assertThrows`, `assertRejects` + +## Common Patterns + +**Temp files:** +```typescript +const workingDir = Deno.makeTempDirSync({ prefix: "quarto-test" }); +// Use workingDir, clean up in teardown +``` + +**Fixtures:** +```typescript +const fixtureDir = docs("my-fixture"); // → tests/docs/my-fixture/ +``` + +## Test Organization + +- `tests/smoke//` - Smoke tests by feature +- `tests/unit//` - Unit tests by feature +- `tests/docs//` - Test fixtures + +**Details:** `llm-docs/testing-patterns.md` for comprehensive patterns and examples. diff --git a/.claude/rules/testing/unit-tests.md b/.claude/rules/testing/unit-tests.md deleted file mode 100644 index 32d00996fe..0000000000 --- a/.claude/rules/testing/unit-tests.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -paths: - - "tests/unit/**/*.test.ts" ---- - -# Unit Tests - -TypeScript-based tests for isolated functionality. Tests live in `tests/unit/`. - -## Running Tests - -```bash -# Linux/macOS -./run-tests.sh unit/path.test.ts -./run-tests.sh unit/ # All unit tests - -# Windows -.\run-tests.ps1 unit/path.test.ts -``` - -## Test Structure - -Unit tests use `unitTest()` from `tests/test.ts` with standard assertions: - -```typescript -import { unitTest } from "../test.ts"; -import { assert, assertEquals } from "testing/asserts"; - -// deno-lint-ignore require-await -unitTest("feature - description", async () => { - const result = myFunction(input); - assert(result === expected, "Error message"); -}); -``` - -## Assertions - -From `testing/asserts` (Deno standard library): - -```typescript -import { - assert, // Boolean check - assertEquals, // Deep equality - assertNotEquals, // Deep inequality - assertThrows, // Exception check - assertRejects, // Async exception check -} from "testing/asserts"; -``` - -## Test Organization - -``` -tests/unit/ -├── schema-validation/ # Schema tests -├── mapped-strings/ # String mapping tests -├── yaml-intelligence/ # YAML completion tests -└── .test.ts # Feature-specific tests -``` - -Some directories have their own `utils.ts` for shared helpers. - -## Common Patterns - -### Test with temp files - -```typescript -const workingDir = Deno.makeTempDirSync({ prefix: "quarto-test" }); - -unitTest("feature - with temp files", async () => { - const testFile = join(workingDir, "test.txt"); - Deno.writeTextFileSync(testFile, "content"); - - // Test... - - Deno.removeSync(testFile); -}); -``` - -### Test with fixtures - -```typescript -import { docs } from "../utils.ts"; - -const fixtureDir = docs("my-fixture"); - -unitTest("feature - with fixtures", async () => { - // fixtureDir points to tests/docs/my-fixture/ -}); -``` - -### Async tests - -Most tests are marked `async` but don't actually await: - -```typescript -// deno-lint-ignore require-await -unitTest("sync test", async () => { - // Synchronous test code -}); -``` - -For actual async: - -```typescript -unitTest("async test", async () => { - const result = await asyncFunction(); - assertEquals(result, expected); -}); -``` diff --git a/.claude/rules/typescript/cliffy-commands.md b/.claude/rules/typescript/cliffy-commands.md new file mode 100644 index 0000000000..62f6f161e4 --- /dev/null +++ b/.claude/rules/typescript/cliffy-commands.md @@ -0,0 +1,48 @@ +--- +paths: + - "src/command/**/*.ts" +--- + +# Cliffy Command Pattern + +Commands use the Cliffy library and follow this structure: + +```typescript +export const myCommand = new Command() + .name("command-name") + .description("What the command does") + .arguments("[input:string]") + .option("-f, --flag", "Flag description") + .option("-o, --output ", "Option with value") + .example("Basic usage", "quarto command input.qmd") + // deno-lint-ignore no-explicit-any + .action(async (options: any, input?: string) => { + // Implementation + }); +``` + +**Options typed as `any`:** Required due to Cliffy limitations. + +**Registration:** Export from `src/command//cmd.ts`, register in `src/command/command.ts`. + +## Error Handling + +Use custom error classes from `src/core/lib/error.ts`: + +```typescript +import { InternalError, ErrorEx, asErrorEx } from "../core/lib/error.ts"; + +// Programming errors (bugs) +throw new InternalError("This should never happen"); + +// User-facing errors +throw new ErrorEx("User-facing error message"); + +// Normalize unknown errors +try { + riskyOperation(); +} catch (e) { + const err = asErrorEx(e); + error(err.message); +} +``` diff --git a/.claude/rules/typescript/deno-conventions.md b/.claude/rules/typescript/deno-conventions.md deleted file mode 100644 index c13c4dd7d9..0000000000 --- a/.claude/rules/typescript/deno-conventions.md +++ /dev/null @@ -1,212 +0,0 @@ ---- -paths: - - "src/**/*.ts" ---- - -# TypeScript/Deno Development Conventions - -Guidance for developing TypeScript code in the quarto-cli codebase using Deno. - -## Import Patterns - -### Use Import Map Names - -```typescript -// ✅ Correct - use import map names -import { Command } from "cliffy/command/mod.ts"; -import { join, dirname } from "../deno_ral/path.ts"; - -// ❌ Wrong - never hardcode JSR/npm URLs in imports -import { join } from "jsr:/@std/path"; -import { z } from "npm:zod"; -``` - -### Always Include `.ts` Extensions - -Required for Deno module resolution: - -```typescript -// ✅ Correct -import { debug } from "../../deno_ral/log.ts"; - -// ❌ Wrong - no extension -import { debug } from "../../deno_ral/log"; -``` - -## Deno RAL (Runtime Abstraction Layer) - -Always import from `src/deno_ral/` rather than using standard library directly: - -```typescript -// ✅ Correct - use abstraction layer -import { join, dirname } from "../deno_ral/path.ts"; -import { existsSync, ensureDirSync } from "../deno_ral/fs.ts"; -import { info, warning, error } from "../deno_ral/log.ts"; - -// ❌ Wrong - direct std lib import -import { join } from "jsr:/@std/path"; -``` - -**Key deno_ral modules:** -- `deno_ral/fs.ts` - File system operations -- `deno_ral/path.ts` - Path utilities -- `deno_ral/log.ts` - Logging (debug, info, warning, error) -- `deno_ral/platform.ts` - Platform detection (`isWindows`) -- `deno_ral/process.ts` - Process execution - -## Deno APIs vs Node.js - -**Use Deno APIs directly**, not Node.js equivalents: - -```typescript -// ✅ Correct - Deno APIs -Deno.env.get("PATH"); -Deno.cwd(); -Deno.readTextFileSync(path); -Deno.writeTextFileSync(path, content); -Deno.statSync(path); -Deno.makeTempDirSync({ prefix: "quarto" }); -Deno.removeSync(path, { recursive: true }); - -// ❌ Wrong - Node.js patterns -process.env.PATH; -process.cwd(); -fs.readFileSync(path); -``` - -## Sync vs Async - -**Prefer sync APIs** in CLI command handlers for simpler code flow: - -```typescript -// ✅ Typical CLI command - sync is fine -const content = Deno.readTextFileSync(path); -const parsed = JSON.parse(content); - -// ✅ Use async for I/O-heavy operations -const results = await Promise.all([ - fetchRemoteData(url1), - fetchRemoteData(url2), -]); -``` - -## Cliffy Command Pattern - -Commands follow this structure: - -```typescript -export const myCommand = new Command() - .name("command-name") - .description("What the command does") - .arguments("[input:string]") - .option("-f, --flag", "Flag description") - .option("-o, --output ", "Option with value") - .example("Basic usage", "quarto command input.qmd") - // deno-lint-ignore no-explicit-any - .action(async (options: any, input?: string) => { - // Implementation - }); -``` - -**Note:** Options must be typed as `any` due to Cliffy limitations. - -**Registration:** Export from `src/command//cmd.ts`, register in `src/command/command.ts`. - -## Error Handling - -Use custom error classes from `src/core/lib/error.ts`: - -```typescript -import { InternalError, ErrorEx } from "../core/lib/error.ts"; - -// Programming errors (bugs) -throw new InternalError("This should never happen"); - -// General errors with better stack traces -throw new ErrorEx("User-facing error message"); - -// Normalize unknown errors -try { - riskyOperation(); -} catch (e) { - const err = asErrorEx(e); - error(err.message); -} -``` - -## Lint Directives - -Use sparingly, only when necessary: - -```typescript -// deno-lint-ignore no-explicit-any -.action(async (options: any, input?: string) => { - // Cliffy requires any for options -}); -``` - -Common directives: -- `no-explicit-any` - For Cliffy options, JSON parsing -- `no-control-regex` - For regex with control characters - -## Path Handling - -```typescript -import { normalizePath } from "../core/path.ts"; -import { isWindows } from "../deno_ral/platform.ts"; - -// Normalize paths for consistent comparison -const normalized = normalizePath(Deno.cwd()); - -// Platform-specific logic -if (isWindows) { - // Windows-specific handling -} -``` - -## Common Utilities - -**Temp files:** (`src/core/temp.ts`) -```typescript -import { createTempContext, globalTempContext } from "../core/temp.ts"; - -// Scoped temp context with cleanup -const temp = createTempContext(); -try { - const tempFile = temp.createFile({ suffix: ".json" }); - // Use tempFile... -} finally { - temp.cleanup(); -} -``` - -**Process execution:** (`src/core/process.ts`) -```typescript -import { execProcess } from "../core/process.ts"; - -const result = await execProcess({ - cmd: ["pandoc", "--version"], - stdout: "piped", -}); -``` - -## Module Loading Order - -`src/quarto.ts` loads monkey patches first: -```typescript -import "./core/deno/monkey-patch.ts"; // Must be first! -// ... rest of imports -``` - -This ensures compatibility shims are loaded before any code runs. - -## Key Conventions Summary - -1. **Use import map names** - Never hardcode JSR/npm URLs -2. **Include `.ts` extensions** - Required for Deno resolution -3. **Import from deno_ral** - Not directly from std library -4. **Use Deno APIs** - No Node.js equivalents -5. **Prefer sync APIs** - Simpler code flow for CLI -6. **Type options as `any`** - Cliffy limitation -7. **Use custom error classes** - Better error handling -8. **Normalize paths** - Use `normalizePath()` for consistency diff --git a/.claude/rules/typescript/deno-essentials.md b/.claude/rules/typescript/deno-essentials.md new file mode 100644 index 0000000000..32a5a7d108 --- /dev/null +++ b/.claude/rules/typescript/deno-essentials.md @@ -0,0 +1,82 @@ +--- +paths: + - "src/**/*.ts" +--- + +# TypeScript/Deno Essentials + +Core conventions for all TypeScript code in quarto-cli. + +## Import Patterns + +### Use Import Map Names + +```typescript +// Correct - use import map names +import { Command } from "cliffy/command/mod.ts"; +import { join, dirname } from "../deno_ral/path.ts"; + +// Wrong - never hardcode JSR/npm URLs +import { join } from "jsr:/@std/path"; +``` + +### Always Include `.ts` Extensions + +```typescript +// Correct +import { debug } from "../../deno_ral/log.ts"; + +// Wrong - no extension +import { debug } from "../../deno_ral/log"; +``` + +## Deno RAL (Runtime Abstraction Layer) + +Import from `src/deno_ral/` instead of standard library directly: + +```typescript +// Correct +import { join, dirname } from "../deno_ral/path.ts"; +import { existsSync } from "../deno_ral/fs.ts"; +import { info, warning } from "../deno_ral/log.ts"; + +// Wrong - direct std lib import +import { join } from "jsr:/@std/path"; +``` + +Key modules: `fs.ts`, `path.ts`, `log.ts`, `platform.ts`, `process.ts` + +## Deno APIs vs Node.js + +Use Deno APIs directly: + +```typescript +// Correct - Deno APIs +Deno.env.get("PATH"); +Deno.cwd(); +Deno.readTextFileSync(path); +Deno.writeTextFileSync(path, content); +Deno.makeTempDirSync({ prefix: "quarto" }); + +// Wrong - Node.js patterns +process.env.PATH; +fs.readFileSync(path); +``` + +## Sync vs Async + +Prefer sync APIs in CLI handlers for simpler code flow. Use async for I/O-heavy parallel operations. + +## Lint Directives + +Common directives (use sparingly): +- `no-explicit-any` - For Cliffy options, JSON parsing +- `no-control-regex` - For regex with control characters + +## Key Conventions + +1. **Use import map names** - Never hardcode JSR/npm URLs +2. **Include `.ts` extensions** - Required for Deno resolution +3. **Import from deno_ral** - Not directly from std library +4. **Use Deno APIs** - No Node.js equivalents +5. **Prefer sync APIs** - Simpler code flow for CLI diff --git a/.claude/rules/typescript/deno-ral.md b/.claude/rules/typescript/deno-ral.md new file mode 100644 index 0000000000..df07d7dd82 --- /dev/null +++ b/.claude/rules/typescript/deno-ral.md @@ -0,0 +1,58 @@ +--- +paths: + - "src/deno_ral/**/*.ts" +--- + +# Deno RAL (Runtime Abstraction Layer) + +The `deno_ral/` directory provides a runtime abstraction layer over Deno's standard library. All code should import from here instead of directly from std. + +## Available Modules + +| Module | Purpose | Key exports | +|--------|---------|-------------| +| `fs.ts` | File system | `existsSync`, `ensureDirSync`, `copySync` | +| `path.ts` | Path utilities | `join`, `dirname`, `basename`, `extname` | +| `log.ts` | Logging | `debug`, `info`, `warning`, `error` | +| `platform.ts` | Platform detection | `isWindows` | +| `process.ts` | Process execution | Process utilities | + +## Why Use deno_ral + +- Consistent API across the codebase +- Abstraction allows future runtime changes +- Import map resolution works correctly +- Avoids scattered `jsr:/@std/*` imports + +## Common Utilities + +**Temp files** (`src/core/temp.ts`): +```typescript +import { createTempContext } from "../core/temp.ts"; + +const temp = createTempContext(); +try { + const tempFile = temp.createFile({ suffix: ".json" }); +} finally { + temp.cleanup(); +} +``` + +**Process execution** (`src/core/process.ts`): +```typescript +import { execProcess } from "../core/process.ts"; + +const result = await execProcess({ + cmd: ["pandoc", "--version"], + stdout: "piped", +}); +``` + +## Module Loading Order + +`src/quarto.ts` loads monkey patches first: +```typescript +import "./core/deno/monkey-patch.ts"; // Must be first! +``` + +This ensures compatibility shims load before any code runs. From 3fb65541de955babe44c21a5183cc1b663f73960 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 5 Feb 2026 18:51:11 +0100 Subject: [PATCH 07/16] Add path-scoped changelog conventions to rules Create .claude/rules/changelog.md with comprehensive guidance for news/ files including section hierarchy, entry format, backports, and regression fix handling. Update CLAUDE.md to point to the new rules file while keeping essential context. Co-Authored-By: Claude Opus 4.5 --- .claude/CLAUDE.md | 10 ++--- .claude/rules/changelog.md | 91 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 7 deletions(-) create mode 100644 .claude/rules/changelog.md diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index d72474fe29..4cce8cf1e7 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -244,13 +244,9 @@ LaTeX error pattern maintenance is documented in [dev-docs/tinytex-pattern-maint ### Changelog Conventions -- Check `configuration` file for current version -- Add entries to `news/changelog-{version}.md` (e.g., `changelog-1.9.md` for version 1.9) -- Format: `- ([#issue](url)): Description.` -- Distinguish between: - - **Fixes**: Fixing bugs or broken functionality (e.g., "Fix `icon=false` not working...") - - **Enhancements**: Adding new features or support (e.g., "Add support for `icon=false`...") -- Group entries by format/feature area (typst, html, website, pdf, etc.) +- Changelog files live in `news/changelog-{version}.md` (e.g., `changelog-1.9.md`) +- Check `configuration` file for current `QUARTO_VERSION` +- See `.claude/rules/changelog.md` for comprehensive conventions (section hierarchy, entry format, backports, regression fixes) ## Key File Paths diff --git a/.claude/rules/changelog.md b/.claude/rules/changelog.md new file mode 100644 index 0000000000..7413cf6991 --- /dev/null +++ b/.claude/rules/changelog.md @@ -0,0 +1,91 @@ +--- +paths: + - news/** +--- + +# Changelog Conventions + +## File Organization + +- One file per major.minor version: `news/changelog-{version}.md` (e.g., `changelog-1.9.md`) +- Check `configuration` file for current `QUARTO_VERSION` +- First line: `All changes included in {version}:` + +## Section Hierarchy (strict order) + +1. **`## Regression fixes`** - Always FIRST if present +2. **`## Dependencies`** - Bundled tool updates +3. **`## Formats`** - By output format (H3 subsections) +4. **`## Projects`** - By project type (H3 subsections) +5. **`## Publishing`** - By platform (H3 subsections) +6. **`## Lua API`** - Filter API changes +7. **`## Commands`** - CLI commands (H3 subsections) +8. **`## Extensions`** - Extension system changes +9. **`## Engines`** - Execution engines (H3 subsections) +10. **`## Other fixes and improvements`** - Always LAST + +### Format Subsections +Use H3 headings with backtick-wrapped names: +```markdown +### `html` +### `typst` +### `pdf` +``` + +## Entry Format + +```markdown +- ([#issue](url)): Description ending with period. +``` + +For external contributors (not core team): +```markdown +- ([#issue](url)): Description. (author: @username) +``` + +**Variations:** +- Pull requests: `([#13441](https://github.com/quarto-dev/quarto-cli/pull/13441))` +- External repos: `([rstudio/tinytex-releases#49](url))` +- No issue/PR (rare): Reference commit hash instead: `([commit](https://github.com/quarto-dev/quarto-cli/commit/abc123))` + +## Writing Entries + +**Language patterns:** +- **Fixes:** Start with "Fix" - describe what was broken + - "Fix `icon=false` not working with typst format." +- **Enhancements:** Start with "Add" or "Support" - describe what was added + - "Add support for `icon=false` in callouts." +- **Updates:** Start with "Update" + - "Update `pandoc` to 3.8.3" + +**Style:** +- Use backticks for code/options: `` `icon=false` `` +- Period at end of every description +- Author attribution `(author: @username)` for **external contributors only** - do NOT add for quarto-cli core team members + +## Regression Fixes + +**What qualifies:** Bugs introduced in recent versions (same major.minor) + +**Placement:** Always at TOP of the file, before Dependencies + +**Workflow:** If you initially place an entry elsewhere and later determine it's a regression, move it to the Regression fixes section + +## Backports + +When a fix is backported to a stable branch: +1. Entry exists in current version changelog (e.g., `changelog-1.9.md`) +2. **Also add entry to stable version changelog** (e.g., `changelog-1.8.md`) + +## What NOT to Put Here + +**Highlights** are NOT changelog entries. They are: +- Promotional content for release announcements +- Managed in quarto-web: `docs/prerelease/{version}/_highlights.qmd` +- Separate from technical changelog + +## Publication + +Changelogs are published to quarto.org via: +- GitHub release attaches `changelog.md` asset +- quarto-web fetches and displays at `/docs/download/changelog/{version}/` From f9215ec30253a7e210e6d5b803f7b9f950f11129 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Fri, 6 Feb 2026 12:03:16 +0100 Subject: [PATCH 08/16] some adjustement to specific rules --- .claude/rules/testing/smoke-all-tests.md | 11 +++++++++++ .claude/rules/typescript/deno-essentials.md | 15 +++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/.claude/rules/testing/smoke-all-tests.md b/.claude/rules/testing/smoke-all-tests.md index acbe037e0a..a06be9e17c 100644 --- a/.claude/rules/testing/smoke-all-tests.md +++ b/.claude/rules/testing/smoke-all-tests.md @@ -104,6 +104,17 @@ _quarto: Valid OS values: `linux`, `darwin`, `windows` +## Pattern Specificity + +Avoid patterns that match template boilerplate instead of document content: + +- Bad: `'#figure\('` - matches any figure including template definitions +- Good: `'#figure\(\[(\r\n?|\n)#block\['` - matches specific document structure + +**Line breaks:** `(\r\n?|\n)` for exact line breaks; `\s*` or `\s+` for flexible whitespace. + +**Examples:** `tests/docs/smoke-all/typst/`, `tests/docs/smoke-all/crossrefs/` + ## YAML String Escaping for Regex **Details:** `llm-docs/testing-patterns.md` → "YAML String Escaping for Regex" diff --git a/.claude/rules/typescript/deno-essentials.md b/.claude/rules/typescript/deno-essentials.md index 32a5a7d108..d70ea9a9b3 100644 --- a/.claude/rules/typescript/deno-essentials.md +++ b/.claude/rules/typescript/deno-essentials.md @@ -67,6 +67,21 @@ fs.readFileSync(path); Prefer sync APIs in CLI handlers for simpler code flow. Use async for I/O-heavy parallel operations. +## Path Handling + +```typescript +import { normalizePath } from "../core/path.ts"; +import { isWindows } from "../deno_ral/platform.ts"; + +// Normalize paths for consistent comparison +const normalized = normalizePath(Deno.cwd()); + +// Platform-specific logic +if (isWindows) { + // Windows-specific handling +} +``` + ## Lint Directives Common directives (use sparingly): From 41cbbf6a47856c0263307c156eb1530c815457f8 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Tue, 10 Feb 2026 11:42:58 +0100 Subject: [PATCH 09/16] Add more scoped rules --- .claude/rules/dev-tools/dev-call-commands.md | 96 ++++++++++++++++++++ .claude/rules/schemas/zod-usage.md | 67 ++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 .claude/rules/dev-tools/dev-call-commands.md create mode 100644 .claude/rules/schemas/zod-usage.md diff --git a/.claude/rules/dev-tools/dev-call-commands.md b/.claude/rules/dev-tools/dev-call-commands.md new file mode 100644 index 0000000000..af6dcb05f7 --- /dev/null +++ b/.claude/rules/dev-tools/dev-call-commands.md @@ -0,0 +1,96 @@ +--- +description: "Development commands reference for Quarto CLI development" +paths: + - "src/command/dev-call/**/*" +--- + +# Development Commands (dev-call) + +The `dev-call` command provides access to internal development tools. These commands are hidden from regular help output but essential for CLI development. + +## cli-info + +Generate JSON information about the Quarto CLI structure and commands. + +```bash +package/dist/bin/quarto dev-call cli-info # Linux/macOS +package/dist/bin/quarto.cmd dev-call cli-info # Windows +``` + +**Use cases**: Documentation generation, programmatic analysis of CLI structure, building CLI tooling. + +## validate-yaml + +Validate YAML files against Quarto's schema definitions. + +```bash +# Validate against built-in schema +package/dist/bin/quarto dev-call validate-yaml config.yml --schema document +package/dist/bin/quarto dev-call validate-yaml _quarto.yml --schema project/project + +# Validate against custom schema file +package/dist/bin/quarto dev-call validate-yaml custom.yml --schema my-schema.yml + +# Get JSON output +package/dist/bin/quarto dev-call validate-yaml config.yml --schema document --json +``` + +**Use cases**: Testing schema changes, debugging YAML configuration issues, validating custom schemas, CI/CD pipelines. + +**Schema names**: Reference definitions in `src/resources/schema/definitions.yml` (e.g., `document`, `project/project`, `format/html`). + +## build-artifacts + +Regenerate schemas, types, and editor tooling files. + +```bash +package/dist/bin/quarto dev-call build-artifacts # Linux/macOS +package/dist/bin/quarto.cmd dev-call build-artifacts # Windows +``` + +**Regenerates**: +- JSON schemas in `src/resources/schema/json-schemas.json` +- Zod schemas in `src/resources/types/zod/schema-types.ts` +- TypeScript type definitions in `src/resources/types/schema-types.ts` +- Editor tooling files (VSCode IntelliSense, YAML intelligence) + +## show-ast-trace + +Launch interactive viewer to visualize how a document transforms through the Lua filter pipeline. + +```bash +package/dist/bin/quarto dev-call show-ast-trace document.qmd +package/dist/bin/quarto dev-call show-ast-trace document.qmd --to html +``` + +**What it does**: + +1. Renders document with AST tracing enabled +2. Generates `-quarto-ast-trace.json` in cache +3. Launches interactive trace viewer in browser + +**Use cases**: Debugging Lua filter behavior, understanding AST transformations, investigating rendering issues, visualizing document structure changes. + +**Alternative**: Manually set `QUARTO_TRACE_FILTERS` environment variable during render. See `dev-docs/lua-filter-trace-viewer.qmd` for detailed guide. + +**Limitations**: + +- Doesn't work well with website/book projects (env var doesn't differentiate per file) +- Shows only Pandoc AST, not postprocessor or writer behavior + +## make-ast-diagram + +Create a static visual diagram of the Pandoc AST structure for a document. + +```bash +package/dist/bin/quarto dev-call make-ast-diagram document.qmd +package/dist/bin/quarto dev-call make-ast-diagram document.qmd --mode full +``` + +**Use cases**: Understanding document AST structure, debugging complex layouts, visualizing nested elements, teaching Pandoc AST concepts. + +**Note**: Simpler than full trace - provides static snapshot rather than filter chain progression. + +## Reference + +See `dev-docs/dev-call-commands.md` for comprehensive reference and workflows. diff --git a/.claude/rules/schemas/zod-usage.md b/.claude/rules/schemas/zod-usage.md new file mode 100644 index 0000000000..1c3f3e61bb --- /dev/null +++ b/.claude/rules/schemas/zod-usage.md @@ -0,0 +1,67 @@ +--- +paths: + - "src/resources/schema/**/*" + - "src/resources/types/**/*" + - "src/core/brand/**/*" + - "src/project/**/*" +--- + +# Zod Schema Usage Patterns + +Guidance on when to use Zod validation vs type assertions in the Quarto codebase. + +## Overview + +Zod schemas are generated from YAML schema definitions but have **specific use cases**. Understanding when to use each approach is critical. + +## When Zod IS Used (Entry Points for External Data) + +Use `Zod.*.parse()` for runtime validation when: + +- Loading brand data from `_brand.yml` files ([brand.ts:65](src/core/brand/brand.ts)) +- Processing extension metadata ([project-context.ts:128](src/project/project-context.ts)) +- Parsing metadata from files at boundaries ([project-shared.ts:606](src/project/project-shared.ts)) +- Validating complex navigation structures ([book-config.ts:262](src/project/types/book/book-config.ts)) + +```typescript +// Example: Validating external data at entry points +const brand = Zod.BrandSingle.parse(externalBrandData); +const navItem = Zod.NavigationItemObject.parse(item); +``` + +## When Type Assertions ARE Used (Internal Processing) + +Most code works with `project.config` which has **already been validated** when loaded via `readAndValidateYamlFromFile()` ([project-context.ts:463](src/project/project-context.ts)). In these cases, use type assertions with `Metadata`: + +```typescript +// Standard pattern for already-validated config data +const siteMeta = project.config?.[kWebsite] as Metadata; +if (siteMeta) { + const configValue = siteMeta[kConfigKey]; + if (typeof configValue === "object") { + const configMeta = configValue as Metadata; + const property = configMeta[kPropertyName] as string; + // ... use property + } +} +``` + +## Key Principle + +YAML schema validation happens at config load time, so downstream code doesn't need redundant Zod validation. Reserve Zod for true entry points where external/untrusted data enters the system. + +## File Locations + +- YAML schema definitions: `src/resources/schema/definitions.yml` +- Generated Zod schemas: `src/resources/types/zod/schema-types.ts` +- Generated TypeScript types: `src/resources/types/schema-types.ts` +- JSON schemas: `src/resources/schema/json-schemas.json` + +## Regenerating Schemas + +After modifying YAML schema definitions: + +```bash +package/dist/bin/quarto dev-call build-artifacts # Linux/macOS +package/dist/bin/quarto.cmd dev-call build-artifacts # Windows +``` From d7e17ec980d9228eae60b851be0fecbfa144d6a6 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Tue, 10 Feb 2026 18:32:24 +0100 Subject: [PATCH 10/16] add more example of fs.ts usage --- .claude/rules/typescript/deno-ral.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.claude/rules/typescript/deno-ral.md b/.claude/rules/typescript/deno-ral.md index df07d7dd82..b0678808f3 100644 --- a/.claude/rules/typescript/deno-ral.md +++ b/.claude/rules/typescript/deno-ral.md @@ -11,7 +11,7 @@ The `deno_ral/` directory provides a runtime abstraction layer over Deno's stand | Module | Purpose | Key exports | |--------|---------|-------------| -| `fs.ts` | File system | `existsSync`, `ensureDirSync`, `copySync` | +| `fs.ts` | File system | `existsSync`, `ensureDirSync`, `copySync`, `safeMoveSync`, `safeRemoveSync` | | `path.ts` | Path utilities | `join`, `dirname`, `basename`, `extname` | | `log.ts` | Logging | `debug`, `info`, `warning`, `error` | | `platform.ts` | Platform detection | `isWindows` | @@ -48,6 +48,14 @@ const result = await execProcess({ }); ``` +## Safe File Operations + +`deno_ral/fs.ts` provides safe wrappers over raw Deno APIs. Prefer these: + +- **`safeMoveSync(src, dest)`** — Use instead of `Deno.renameSync`. Handles cross-device moves by falling back to copy+delete on `EXDEV` errors. +- **`safeRemoveSync(path, options)`** — Use instead of `Deno.removeSync`. Tolerates already-removed paths (no error if file doesn't exist). +- **`safeRemoveDirSync(path, boundary)`** — Safe recursive removal that refuses to delete outside the boundary directory. + ## Module Loading Order `src/quarto.ts` loads monkey patches first: From 8383a303ee0ccad2d62692aad466fb9be0bc8b79 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Fri, 20 Feb 2026 17:44:09 +0100 Subject: [PATCH 11/16] Add flaky test debugging methodology to dev-docs Migrate content from personal gist into project documentation. Methodology covers 6 phases (reproduce, binary search, narrow down, state change, root cause, verify) with the #13647 TinyTeX/elsarticle case study as a concrete worked example. Co-Authored-By: Claude Opus 4.6 --- dev-docs/debugging-flaky-tests.md | 247 ++++++++++++++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 dev-docs/debugging-flaky-tests.md diff --git a/dev-docs/debugging-flaky-tests.md b/dev-docs/debugging-flaky-tests.md new file mode 100644 index 0000000000..67e30dc33e --- /dev/null +++ b/dev-docs/debugging-flaky-tests.md @@ -0,0 +1,247 @@ +# Debugging Flaky Tests: A Systematic Approach + +A methodology for debugging tests that fail intermittently in CI or when run as part of a test suite, but pass when run in isolation. + +## Problem Pattern + +Tests that hang or timeout when run in CI or as part of a test suite, but work fine when run alone. Common root cause categories: + +1. **State pollution**: One test modifies global state that affects subsequent tests +2. **Resource leaks**: File handles, processes, or network connections not cleaned up +3. **Environment corruption**: Package managers (TinyTeX, npm, etc.) get into inconsistent state +4. **Timing/race conditions**: Tests depend on specific execution order or timing + +## Investigation Methodology + +### Phase 1: Reproduce Locally + +**Goal**: Confirm you can reproduce the issue outside of CI. + +1. Identify the failing test bucket from CI logs +2. Extract the test file list from CI configuration +3. Create a test script to run the bucket sequentially: + +```bash +# array.sh - Run tests sequentially +readarray -t my_array < <(echo '[...]' | jq -rc '.[]') +haserror=0 +for file in "${my_array[@]}"; do + echo ">>> ./run-tests.sh ${file}" + shopt -s globstar && ./run-tests.sh $file + status=$? + [ $status -eq 0 ] && echo ">>> No error" || haserror=1 +done +``` + +4. Run and confirm the hang occurs locally + +### Phase 2: Binary Search to Isolate Culprit + +**Goal**: Find which specific test file causes the issue. + +If test N causes state pollution, tests 1 through N-1 will pass, then the problematic test will occur. + +1. Split your test list in half +2. Run first half + the hanging test: + +```bash +# test-binary-search.sh +readarray -t tests < <(echo '[first_half_tests, "hanging-test.qmd"]' | jq -rc '.[]') +for file in "${tests[@]}"; do + ./run-tests.sh $file || exit 1 +done +``` + +3. If it hangs: culprit is in first half, repeat with first half +4. If it passes: culprit is in second half, repeat with second half +5. Continue until you identify the single test file + +**Example**: In [#13647](https://github.com/quarto-dev/quarto-cli/issues/13647), binary search across 51 tests identified `render-format-extension.test.ts` as the culprit. + +### Phase 3: Narrow Down Within Test File + +**Goal**: Find which specific operation in the test file causes pollution. + +1. Read the test file to understand what it does +2. Identify distinct operations (e.g., rendering different formats) +3. Comment out sections and retest: + +```typescript +// Comment out formats one by one to isolate +// test("academic/document.qmd elsevier-pdf", ...) +// test("academic/document.qmd springer-pdf", ...) +test("academic/document.qmd acm-pdf", ...) +``` + +4. Binary search through the operations to find the specific one + +**Example**: In #13647, rendering `academic/document.qmd` with `elsevier-pdf` format was the specific trigger. + +### Phase 4: Understand the State Change + +**Goal**: Determine what environmental change causes the issue. + +Common suspects: package installations (TinyTeX, npm, pip), configuration file modifications, cache pollution, file system changes. + +1. Create a clean test environment (fresh TinyTeX install) +2. Take snapshots before/after the problematic operation: + +```bash +# Before snapshot +tlmgr list --only-installed > before.txt + +# Run problematic test +./run-tests.sh problematic-test.ts + +# After snapshot +tlmgr list --only-installed > after.txt +diff before.txt after.txt +``` + +3. For TinyTeX issues, check: + - Installed packages: `tlmgr list --only-installed` + - Package versions: `tlmgr info ` + - Format files: `ls -la $(kpsewhich -var-value TEXMFSYSVAR)/web2c/luatex/` + - What `tlmgr update --all` installs + +**Example**: In #13647, `elsevier-pdf` rendering triggered `tlmgr update --all` which updated core packages and regenerated lualatex format files. The format regeneration expected modern conventions that conflicted with the bundled class file. + +### Phase 5: Identify Root Cause + +**Goal**: Understand WHY the state change causes the failure. + +1. Compare working vs broken states in detail +2. For package version issues: + - Check if test bundles old versions of libraries/classes + - Compare with system-installed versions + - Review changelogs between versions +3. Create minimal reproduction: + +```bash +# verify-root-cause.sh +echo "=== Test 1: Old version ===" +# Setup with old version, run problematic operation, run hanging test + +echo "=== Test 2: New version ===" +# Setup with new version, run problematic operation, run hanging test +``` + +**Example**: In #13647, the bundled `elsarticle.cls v3.3` was missing `\RequirePackage[T1]{fontenc}`. TinyTeX's `elsarticle.cls v3.4c` includes it. The font encoding mismatch corrupted lualatex format files, causing subsequent lualatex renders to hang. + +### Phase 6: Verify Solution + +**Goal**: Confirm your fix resolves the issue. + +1. Apply the fix (update package, patch code, etc.) +2. Create verification script: + +```bash +#!/bin/bash +echo ">>> Fresh environment setup" +# Clean install + +echo ">>> Running problematic test (with fix)" +./run-tests.sh problematic-test.ts || exit 1 + +echo ">>> Testing previously-hanging test" +./run-tests.sh hanging-test.qmd || exit 1 + +echo "SUCCESS: Fix verified!" +``` + +3. Run multiple times to ensure consistency +4. Test with clean environment each time (critical for environment pollution issues) + +## Key Debugging Tools + +### TinyTeX + +```bash +# List installed packages +tlmgr list --only-installed + +# Check package info +tlmgr info + +# Find file locations +kpsewhich elsarticle.cls + +# Check format files +ls -la $(kpsewhich -var-value TEXMFSYSVAR)/web2c/luatex/ + +# Clean TinyTeX (for fresh start) +rm -rf ~/.TinyTeX +quarto install tinytex +``` + +### Test Isolation + +```bash +# Run single test +./run-tests.sh path/to/test.ts + +# Run test sequence to reproduce ordering issues +for test in test1.ts test2.ts test3.ts; do + ./run-tests.sh $test || break +done +``` + +### Package/Dependency Comparison + +```bash +# Compare package versions +npm list +tlmgr list --only-installed +pip list + +# Check for bundled vs system versions +find . -name "*.cls" -o -name "*.sty" +``` + +## Best Practices + +1. **Always reproduce locally first** - CI is too slow for iterative debugging +2. **Use binary search** - Most efficient way to isolate culprits in large test suites +3. **Test with clean environments** - Especially for environment pollution issues +4. **Take snapshots** - Before/after comparisons are invaluable +5. **Create verification scripts** - Automate testing your fix +6. **Document the root cause** - Help others understand the issue + +## Common Pitfalls + +1. **Testing with polluted environment** - Always start fresh for environment issues +2. **Assuming causation from correlation** - Just because test A runs before test B doesn't mean A causes B's failure +3. **Stopping too early** - Finding the problematic test isn't enough; understand WHY it causes issues +4. **Not verifying the fix** - Always confirm your solution actually works + +## Checklist + +- [ ] Reproduce the issue locally +- [ ] Identify the specific test bucket that triggers the issue +- [ ] Use binary search to isolate the culprit test file +- [ ] Narrow down to specific operation within the test +- [ ] Take environment snapshots before/after +- [ ] Identify what environmental change occurs +- [ ] Understand WHY the change causes the failure +- [ ] Develop and apply a fix +- [ ] Verify the fix with clean environments +- [ ] Document the root cause and solution + +## Case Study: #13647 (tufte.qmd Hanging in CI) + +**Symptom**: `tufte.qmd` hangs after 10+ minutes when run after a bucket of tests. Same document renders fine in ~30s when run alone. Lualatex engine stuck during "running lualatex - 1". + +**Investigation summary**: + +```bash +# Binary search: 51 tests → render-format-extension.test.ts +# Narrow down: elsevier-pdf format was the trigger +# State change: tlmgr update --all regenerated format files +# Root cause: Bundled elsarticle.cls v3.3 missing fontenc, corrupting lualatex formats +# Fix: Update extension to use elsarticle.cls v3.4c +``` + +**References**: +- [quarto-dev/quarto-cli#13647](https://github.com/quarto-dev/quarto-cli/issues/13647) +- [quarto-journals/elsevier#38](https://github.com/quarto-journals/elsevier/pull/38) - Update elsarticle.cls +- [quarto-journals/elsevier#40](https://github.com/quarto-journals/elsevier/pull/40) - CTAN update From f0e625ae90f424a70b12e30a2be9c0a171e0d086 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Fri, 20 Feb 2026 17:46:03 +0100 Subject: [PATCH 12/16] Consolidate folder CLAUDE.md files into .claude/rules/ Move three folder-specific CLAUDE.md files into centralized path-scoped rule files: - tests/CLAUDE.md -> .claude/rules/testing/overview.md - src/resources/filters/CLAUDE.md -> .claude/rules/filters/overview.md - llm-docs/CLAUDE.md -> .claude/rules/llm-docs-maintenance.md Each gets paths: frontmatter for conditional loading. Co-Authored-By: Claude Opus 4.6 --- .../rules/filters/overview.md | 7 ++++++- .../rules/llm-docs-maintenance.md | 21 ++++++++++++------- .../rules/testing/overview.md | 17 ++++++++------- 3 files changed, 28 insertions(+), 17 deletions(-) rename src/resources/filters/CLAUDE.md => .claude/rules/filters/overview.md (93%) rename llm-docs/CLAUDE.md => .claude/rules/llm-docs-maintenance.md (81%) rename tests/CLAUDE.md => .claude/rules/testing/overview.md (82%) diff --git a/src/resources/filters/CLAUDE.md b/.claude/rules/filters/overview.md similarity index 93% rename from src/resources/filters/CLAUDE.md rename to .claude/rules/filters/overview.md index 146841534b..fc1f410431 100644 --- a/src/resources/filters/CLAUDE.md +++ b/.claude/rules/filters/overview.md @@ -1,6 +1,11 @@ +--- +paths: + - "src/resources/filters/**" +--- + # Lua Filter System -This directory contains Quarto's Lua filter system - a multi-stage document transformation pipeline that processes Pandoc AST through ~212 Lua files. +Quarto's Lua filter system is a multi-stage document transformation pipeline that processes Pandoc AST through ~212 Lua files. **For coding conventions:** See `.claude/rules/filters/lua-development.md` diff --git a/llm-docs/CLAUDE.md b/.claude/rules/llm-docs-maintenance.md similarity index 81% rename from llm-docs/CLAUDE.md rename to .claude/rules/llm-docs-maintenance.md index 823439367f..16d5b97dde 100644 --- a/llm-docs/CLAUDE.md +++ b/.claude/rules/llm-docs-maintenance.md @@ -1,6 +1,11 @@ -# LLM Documentation +--- +paths: + - "llm-docs/**" +--- + +# LLM Documentation Maintenance -This directory contains documentation written for LLM assistants working on the Quarto codebase. +The `llm-docs/` directory contains documentation written for LLM assistants working on the Quarto codebase. These docs capture architectural understanding that would otherwise require extensive codebase exploration. ## Staleness Check @@ -41,12 +46,12 @@ Then update `main_commit`, `analyzed_date`, and verify `key_files` list is compl ## Document Purpose -These docs capture architectural understanding that would otherwise require extensive codebase exploration. They are NOT: -- User documentation (that's at quarto.org) -- Code comments (those live in source files) -- Issue-specific notes (those go in PR descriptions) - -They ARE: +These docs are: - Architectural overviews for AI assistants - File location maps for common tasks - Pattern documentation for consistency + +They are NOT: +- User documentation (that's at quarto.org) +- Code comments (those live in source files) +- Issue-specific notes (those go in PR descriptions) diff --git a/tests/CLAUDE.md b/.claude/rules/testing/overview.md similarity index 82% rename from tests/CLAUDE.md rename to .claude/rules/testing/overview.md index a59f80facb..bec82f921b 100644 --- a/tests/CLAUDE.md +++ b/.claude/rules/testing/overview.md @@ -1,6 +1,11 @@ +--- +paths: + - "tests/**" +--- + # Test Infrastructure -This directory contains Quarto's test suite. For comprehensive documentation, see `README.md`. +Quarto's test suite lives in `tests/`. For comprehensive documentation, see `tests/README.md`. ## Running Tests @@ -27,8 +32,8 @@ $env:QUARTO_TESTS_NO_CONFIG=$true; .\run-tests.ps1 # Windows | Type | Location | File Pattern | Details | |------|----------|--------------|---------| -| Unit | `tests/unit/` | `*.test.ts` | `.claude/rules/testing/unit-tests.md` | -| Smoke | `tests/smoke/` | `*.test.ts` | `.claude/rules/testing/smoke-tests.md` | +| Unit | `tests/unit/` | `*.test.ts` | `.claude/rules/testing/typescript-tests.md` | +| Smoke | `tests/smoke/` | `*.test.ts` | `.claude/rules/testing/typescript-tests.md` | | Smoke-all | `tests/docs/smoke-all/` | `*.qmd` | `.claude/rules/testing/smoke-all-tests.md` | | Playwright | `tests/integration/playwright/` | `*.spec.ts` | `.claude/rules/testing/playwright-tests.md` | @@ -63,8 +68,4 @@ Managed via: 1. Run single test to isolate failures 2. Check render output - tests capture stdout/stderr 3. VSCode debugging via `.vscode/launch.json` - -## Related Documentation - -- **Full test docs**: `tests/README.md` -- **Flaky test debugging**: https://gist.github.com/cderv/77405f5a5ea0c1db38693159c4a260dd +4. Flaky test methodology: [dev-docs/debugging-flaky-tests.md](../../../dev-docs/debugging-flaky-tests.md) From f6810cd7fdc3eea4481a686ff07f6954fec47199 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Fri, 20 Feb 2026 17:46:42 +0100 Subject: [PATCH 13/16] Update .claude/CLAUDE.md for consolidation and gist migration - Replace personal gist URL with dev-docs/debugging-flaky-tests.md ref - Deduplicate test running commands (brief summary + pointer to .claude/rules/testing/overview.md) - Update memory file types table (remove folder CLAUDE.md row, add llm-docs row) - Add pointer to dev-docs/claude-code-setup.md Co-Authored-By: Claude Opus 4.6 --- .claude/CLAUDE.md | 37 +++++++++---------------------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 4cce8cf1e7..3af84f9586 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -70,35 +70,14 @@ This command regenerates: ### Running Tests +Tests live in `tests/` and require R, Python, and Julia. See `.claude/rules/testing/overview.md` for commands, test types, dependencies, and debugging tips. + ```bash cd tests - -# Linux/macOS -./run-tests.sh # Run all tests -./run-tests.sh smoke/extensions/extension-render-doc.test.ts # Run specific test -./run-tests.sh smoke/extensions/ # Run test directory -./run-tests.sh docs/smoke-all/2023/01/04/issue-3847.qmd # Run smoke-all test - -# Windows (PowerShell 7+) -.\run-tests.ps1 # Run all tests -.\run-tests.ps1 smoke/extensions/extension-render-doc.test.ts # Run specific test -.\run-tests.ps1 docs/smoke-all/2023/01/04/issue-3847.qmd # Run smoke-all test +./run-tests.sh smoke/render/render.test.ts # Linux/macOS +.\run-tests.ps1 smoke/render/render.test.ts # Windows ``` -**Test Dependencies:** Tests require R, Python, and Julia. Run `configure-test-env.sh` (or `.ps1`) to set up test environments using renv (R), uv (Python), and Pkg.jl (Julia). - -**Skip test configuration:** Set `QUARTO_TESTS_NO_CONFIG=true` to skip dependency setup when running tests. - -### Test Structure - -- `tests/unit/` - Unit tests -- `tests/integration/` - Integration tests -- `tests/smoke/` - Smoke tests -- `tests/docs/` - Test fixtures and sample documents -- `tests/docs/smoke-all/` - Document-based tests using `_quarto` YAML key - -See `tests/README.md` for comprehensive test documentation. - ### Feature Format Matrix The feature format matrix in `dev-docs/feature-format-matrix/` documents and tests feature support across all output formats. @@ -181,7 +160,7 @@ The feature format matrix in `dev-docs/feature-format-matrix/` documents and tes ### Debugging Flaky Tests -Comprehensive methodology for debugging flaky tests documented in: https://gist.github.com/cderv/77405f5a5ea0c1db38693159c4a260dd +Comprehensive methodology for debugging flaky tests documented in [dev-docs/debugging-flaky-tests.md](../dev-docs/debugging-flaky-tests.md). Key phases: 1. Reproduce locally (outside CI) @@ -274,7 +253,7 @@ See CONTRIBUTING.md for pull request guidelines. Significant changes require a s This project uses Claude Code memory files for AI-assisted development. When updating memory files: - **Add new feature area?** Create `.claude/rules//feature-name.md` with `paths:` frontmatter -- **Update existing feature?** Edit the relevant rule file or subfolder CLAUDE.md +- **Update existing feature?** Edit the relevant rule file - **Deep dive doc needed?** Place it in `llm-docs/` and reference from rules **Memory file types:** @@ -283,10 +262,12 @@ This project uses Claude Code memory files for AI-assisted development. When upd |----------|-------------|---------| | `.claude/CLAUDE.md` | Always | Project overview, essential commands | | `.claude/rules//` | When paths match | Feature-specific conventions | -| `/CLAUDE.md` | When reading files in folder | Deep documentation for that area | +| `llm-docs/` | When explicitly read | Architectural deep dives | **Personal overrides:** Create `CLAUDE.local.md` (gitignored) for personal preferences like preferred shell syntax or workflow customizations. This file is loaded alongside the project CLAUDE.md but won't be committed. +For setup details, see [dev-docs/claude-code-setup.md](../dev-docs/claude-code-setup.md). + ## Additional Resources - **LLM Documentation**: `llm-docs/` contains AI-specific guidance for working with the codebase From 2e38c121090b73726042dcb329ff68f09d0e5ff2 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Fri, 20 Feb 2026 17:48:36 +0100 Subject: [PATCH 14/16] Trim Deno RAL overlap in deno-essentials.md Remove duplicated module list, add pointer to deno-ral.md for the full module reference and safe file operations. Co-Authored-By: Claude Opus 4.6 --- .claude/rules/typescript/deno-essentials.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.claude/rules/typescript/deno-essentials.md b/.claude/rules/typescript/deno-essentials.md index d70ea9a9b3..65d4cc78fe 100644 --- a/.claude/rules/typescript/deno-essentials.md +++ b/.claude/rules/typescript/deno-essentials.md @@ -32,20 +32,17 @@ import { debug } from "../../deno_ral/log"; ## Deno RAL (Runtime Abstraction Layer) -Import from `src/deno_ral/` instead of standard library directly: +Import from `src/deno_ral/` instead of standard library directly. See `.claude/rules/typescript/deno-ral.md` for the full module reference, safe file operations, and internals. ```typescript // Correct import { join, dirname } from "../deno_ral/path.ts"; import { existsSync } from "../deno_ral/fs.ts"; -import { info, warning } from "../deno_ral/log.ts"; // Wrong - direct std lib import import { join } from "jsr:/@std/path"; ``` -Key modules: `fs.ts`, `path.ts`, `log.ts`, `platform.ts`, `process.ts` - ## Deno APIs vs Node.js Use Deno APIs directly: From 5d7d747b5eab697a550c6d51ba055b3a7a94d876 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Fri, 20 Feb 2026 17:48:58 +0100 Subject: [PATCH 15/16] Add Claude Code setup guide for contributors Create dev-docs/claude-code-setup.md explaining .claude/ files, path-scoped rules, CLAUDE.local.md overrides, and how to add or update rules. Add AI-Assisted Development section to CONTRIBUTING.md. Co-Authored-By: Claude Opus 4.6 --- CONTRIBUTING.md | 4 ++ dev-docs/claude-code-setup.md | 87 +++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 dev-docs/claude-code-setup.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4805db862d..d2693fe317 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,3 +31,7 @@ Pull requests are very welcome! Here's how to contribute via PR: 3. Submit the [pull request](https://help.github.com/articles/using-pull-requests). It is ok to submit as draft if your are still working on it but would like some feedback from us. It is always good to share in the open that you are working on it. We'll try to be as responsive as possible in reviewing and accepting pull requests. + +## AI-Assisted Development + +This repository includes shared [Claude Code](https://docs.anthropic.com/en/docs/claude-code) memory files in `.claude/` that help AI assistants understand the Quarto codebase. See [dev-docs/claude-code-setup.md](dev-docs/claude-code-setup.md) for details on how these work and how to contribute to them. diff --git a/dev-docs/claude-code-setup.md b/dev-docs/claude-code-setup.md new file mode 100644 index 0000000000..d3f369b551 --- /dev/null +++ b/dev-docs/claude-code-setup.md @@ -0,0 +1,87 @@ +# Claude Code Setup for Quarto Contributors + +This project includes shared [Claude Code](https://docs.anthropic.com/en/docs/claude-code) memory files that help AI assistants understand the Quarto codebase. These files are committed to the repository so all contributors benefit from consistent AI-assisted development. + +## What's Included + +### `.claude/CLAUDE.md` + +Always loaded by Claude Code. Contains the project overview: architecture, setup, build commands, conventions, and key file paths. + +### `.claude/rules/` + +Path-scoped rule files that load conditionally based on what files you're working with. For example, when editing Lua filters, Claude Code automatically loads filter-specific conventions. + +Current rule areas: +- `changelog.md` — Changelog entry format +- `filters/` — Lua filter coding conventions and system overview +- `formats/` — Format handler patterns +- `rendering/` — Render pipeline architecture +- `schemas/` — Zod schema patterns +- `testing/` — Test infrastructure, smoke-all format, Playwright, anti-patterns +- `typescript/` — Deno essentials, RAL, Cliffy commands +- `dev-tools/` — Development commands reference +- `llm-docs-maintenance.md` — LLM documentation staleness checking + +Each rule file has a `paths:` frontmatter that controls when it loads: + +```yaml +--- +paths: + - "src/resources/filters/**" +--- +``` + +This means the file only loads when Claude Code is working with files matching those paths. + +### `llm-docs/` + +Architectural deep-dive documentation for AI assistants. These are NOT auto-loaded — they're read on demand when Claude Code needs detailed understanding of a subsystem. Topics include template systems, error messages, testing patterns, and Lua API reference. + +Each llm-doc has staleness metadata in its frontmatter so Claude Code can check if the documented code has changed since the analysis was done. + +## Personal Overrides + +Create `CLAUDE.local.md` at the repository root for personal overrides. This file is gitignored and won't be committed. Use it for: + +- Preferred shell syntax or platform-specific notes +- Personal workflow customizations +- References to personal tools or configurations + +Claude Code loads `CLAUDE.local.md` alongside the project `CLAUDE.md`. + +## Adding or Updating Rules + +### New rule file + +1. Create `.claude/rules//rule-name.md` +2. Add `paths:` frontmatter listing glob patterns relative to the repo root (e.g., `"src/resources/filters/**"`) +3. Keep rules focused and concise (50-250 lines is typical) + +### Update existing rule + +Edit the relevant file in `.claude/rules/`. The path scoping ensures changes only affect sessions working with matching files. + +### New llm-doc + +1. Create `llm-docs/topic-name.md` +2. Add staleness frontmatter (`main_commit`, `analyzed_date`, `key_files`) +3. Reference from relevant rule files if helpful + +## What's NOT Committed + +The `.gitignore` excludes personal Claude Code files: + +``` +CLAUDE.local.md # Personal overrides +.claude/commands/ # Personal slash commands +.claude/docs/ # Personal documentation +.claude/settings.local.json # Local settings +``` + +These stay personal to each developer. + +## Further Reading + +- [Claude Code documentation](https://docs.anthropic.com/en/docs/claude-code) +- [Memory files reference](https://docs.anthropic.com/en/docs/claude-code/memory) From 3a07595fda9ee677bebb0ea3f85d4aead653063c Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Fri, 20 Feb 2026 18:27:11 +0100 Subject: [PATCH 16/16] Fix link consistency and add security note to setup guide Fix relative link for tinytex-pattern-maintenance.md in .claude/CLAUDE.md (missing ../ prefix). Add note about not committing secrets to dev-docs/claude-code-setup.md. Co-Authored-By: Claude Opus 4.6 --- .claude/CLAUDE.md | 2 +- dev-docs/claude-code-setup.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 3af84f9586..f3fec1886b 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -205,7 +205,7 @@ Key phases: ### LaTeX Error Detection -LaTeX error pattern maintenance is documented in [dev-docs/tinytex-pattern-maintenance.md](dev-docs/tinytex-pattern-maintenance.md). +LaTeX error pattern maintenance is documented in [dev-docs/tinytex-pattern-maintenance.md](../dev-docs/tinytex-pattern-maintenance.md). - Patterns inspired by TinyTeX's comprehensive regex.json - Automated daily verification workflow checks for TinyTeX pattern updates diff --git a/dev-docs/claude-code-setup.md b/dev-docs/claude-code-setup.md index d3f369b551..cc1e48ea5e 100644 --- a/dev-docs/claude-code-setup.md +++ b/dev-docs/claude-code-setup.md @@ -81,6 +81,8 @@ CLAUDE.local.md # Personal overrides These stay personal to each developer. +Avoid committing API keys, tokens, or credentials in any `.claude/` or `llm-docs/` file. Use environment variables or `.env` (also gitignored) for sensitive values. + ## Further Reading - [Claude Code documentation](https://docs.anthropic.com/en/docs/claude-code)