Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions llm-docs/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# LLM Documentation

This directory contains documentation written for LLM assistants working on the Quarto codebase.

## Staleness Check

Each document has YAML frontmatter with analysis metadata:

```yaml
---
main_commit: abc1234 # merge-base with main (stable reference)
analyzed_date: 2025-01-22
key_files:
- path/to/file1.ts
- path/to/file2.lua
---
```

**Why merge-base?** Branch commits can be rebased or disappear. The merge-base with main is stable and represents the baseline from main that was analyzed.

**Before relying on a document**, check if key files have changed:

```bash
git log --oneline <main_commit>..main -- <key_files>
```

If there are significant changes, re-explore the codebase and update the document.

## Updating Documents

After re-analyzing, update the frontmatter:

```bash
# Get merge-base with main (use upstream/main if that's the main remote)
git merge-base HEAD main | cut -c1-9
```

Then update `main_commit`, `analyzed_date`, and verify `key_files` list is complete.

**Date verification:** Before writing dates, check today's date from the system environment (shown at conversation start). This avoids year typos like writing 2025 when it's 2026.

## 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:
- Architectural overviews for AI assistants
- File location maps for common tasks
- Pattern documentation for consistency
228 changes: 228 additions & 0 deletions llm-docs/callout-styling-html.md
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like this idea, but could we add the dates to the filename somehow? And note that Claude thinks it's 2025 :D

We could say 2026/01/22/callout-styling-html.md or 2026-01/2026-01-22-callout-styling-html or something like that. I just think that if we don't put date on the files and start accumulating them, it will get overwhelming quite quickly. And my experience with the kyoto repository makes me want to have at least monthly subdirectories, because you end up with a lot of them!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there are two types of doc:
(1) The design doc for some problem which makes sense to have date in filename. Usually you write them once, and then you don't update them I would say. Only consult them for history.
(2) Generic doc about the codebase to explain part of if, some features, concepts or design. And I would say that we need to keep updated those ones.

I have seen the kyoto ones, and I think there of type (1) right ?
Or type (2) ?

For llm-docs/ folder I thought this was type 2 and I wanted to store knowledge about callout design for HTML for next time we need to work on it. I was thinking we would need to update it, hence the YAML header to keep track of it.

If we were to have date in file, then we would have a lot outdated one right ? or do you update them if needed ?

Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
---
main_commit: 50db5a5b0
analyzed_date: 2026-01-22
key_files:
- src/resources/formats/html/bootstrap/_bootstrap-rules.scss
- src/resources/formats/revealjs/quarto.scss
- src/resources/formats/html/styles-callout.html
- src/resources/filters/customnodes/callout.lua
---

# HTML Callout Styling Architecture

This document describes the CSS architecture for Quarto callouts across HTML-based output formats.

## Overview

Quarto uses a **three-tier callout styling architecture** depending on the output format:

| Tier | Formats | CSS Location | Features |
|------|---------|--------------|----------|
| Bootstrap HTML | `html` (with themes) | `formats/html/bootstrap/_bootstrap-rules.scss` | Full theming, collapsible, dark mode |
| RevealJS | `revealjs` | `formats/revealjs/quarto.scss` | Presentation-specific scaling, slide-aware |
| Standalone HTML | `epub`, `gfm`, plain html | `formats/html/styles-callout.html` | Inline CSS, no dependencies |

All HTML callouts support three **appearance** values:
- **default**: Full-featured with colored header background
- **simple**: Lightweight with left border only
- **minimal**: Maps to simple with `icon=false`

## Format Detection (Lua Filter)

The Lua filter `src/resources/filters/customnodes/callout.lua` selects the appropriate renderer:

```
Renderer selection order:
1. hasBootstrap() → Bootstrap HTML renderer
2. isEpubOutput() || isRevealJsOutput() → Simpler HTML structure
3. isGfmOutput() → GitHub markdown alerts
4. Default → BlockQuote fallback
```

The `hasBootstrap()` function (in `filters/common/pandoc.lua`) checks the `has-bootstrap` parameter set by TypeScript during format initialization.

## HTML Structure by Format

### Bootstrap HTML

```html
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container"><i class='callout-icon'></i></div>
<div class="callout-title-container flex-fill">Title</div>
</div>
<div class="callout-body-container">
<div class="callout-body">Content</div>
</div>
</div>
```

### EPUB/RevealJS HTML

```html
<div class="callout callout-note callout-style-default">
<div class="callout-body">
<div class="callout-icon-container"><i class='callout-icon'></i></div>
<div class="callout-title">Title</div>
<div class="callout-content">Content</div>
</div>
</div>
```

Note: Bootstrap uses `callout-body-container` wrapper and Bootstrap utility classes (`d-flex`, `flex-fill`). EPUB/RevealJS uses a flatter structure.

## Feature Comparison

| Feature | Bootstrap | RevealJS | Standalone |
|---------|-----------|----------|------------|
| Collapsible | Yes | No | No |
| Icon type | SVG (dynamic color) | SVG (dynamic color) | PNG (base64) |
| Theming | Full Bootstrap vars | Presentation vars | Fixed colors |
| Dark mode | Yes | Slide background aware | No |
| Font scaling | Responsive | Presentation-specific (0.7em) | Fixed (0.9rem) |

---

## Bootstrap HTML Styling

File: `src/resources/formats/html/bootstrap/_bootstrap-rules.scss`

### Callout States

| State | CSS Class | Description |
|-------|-----------|-------------|
| Titled | `.callout-titled` | Has a title/header |
| Untitled | `:not(.callout-titled)` | Content only, no header |
| Collapsed | `.callout-header.collapsed` | Collapsible, currently closed |
| Empty content | `.callout-empty-content` | No body content |

### Styling Patterns

**Base callout:**
```scss
.callout {
margin-top: $callout-margin-top;
margin-bottom: $callout-margin-bottom;
border-radius: $border-radius;
}
```

**Simple vs Default appearance:**
- `.callout-style-simple`: Left border only, lighter styling
- `.callout-style-default`: Full border, colored header background

**Body margins** vary by appearance (simple/default) and titled state (titled/untitled). The margin rules handle edge cases like collapsed callouts and empty content states.

### Theming Variables

Bootstrap callouts use SCSS variables (in `_bootstrap-variables.scss`):

```scss
$callout-border-width: 0.4rem !default;
$callout-border-scale: 0% !default;
$callout-icon-scale: 10% !default;
$callout-margin-top: 1.25rem !default;
$callout-margin-bottom: 1.25rem !default;
```

Colors are defined per callout type (note, warning, important, tip, caution) using Bootstrap's color functions.

---

## RevealJS Styling

File: `src/resources/formats/revealjs/quarto.scss`

### Presentation-Specific Adjustments

```scss
// Variables
$callout-border-width: 0.3rem;
$callout-margin-top: 1rem;
$callout-margin-bottom: 1rem;

// Font scaling for slide readability
.reveal div.callout {
font-size: 0.7em;
}
```

### Light/Dark Slide Awareness

RevealJS callouts adjust colors based on slide background using the `shift_to_dark` mixin:

```scss
.has-dark-background div.callout-note {
// Lighter colors for dark backgrounds
}
```

---

## Standalone/EPUB Styling

File: `src/resources/formats/html/styles-callout.html`

### Characteristics

- **Inline CSS** embedded in HTML template
- **PNG icons** (base64-encoded) instead of SVG
- **Fixed colors**: Uses hardcoded `#acacac`, `silver` borders
- **No collapsible support**
- **No theming** - works without Bootstrap or any CSS framework

### Key Selectors

```css
.callout /* Base container */
.callout.callout-style-simple /* Simple bordered style */
.callout.callout-style-default /* Default style with header */
.callout-title /* Title container */
.callout-body /* Content container */
.callout-icon::before /* Icon pseudo-element */
```

---

## CSS Class Reference

Classes applied across all HTML formats:

| Class | Applied When | Purpose |
|-------|--------------|---------|
| `.callout` | Always | Base container |
| `.callout-{type}` | Always | Type: note, warning, important, tip, caution |
| `.callout-style-{appearance}` | Always | Style: default, simple |
| `.callout-titled` | Has title | Structural indicator |
| `.no-icon` | `icon=false` | Suppress icon |
| `.callout-empty-content` | No body | Empty state (Bootstrap only) |

---

## Related Files

### CSS/SCSS

| File | Purpose |
|------|---------|
| `src/resources/formats/html/bootstrap/_bootstrap-rules.scss` | Bootstrap HTML callout styles |
| `src/resources/formats/html/bootstrap/_bootstrap-variables.scss` | Bootstrap callout variables |
| `src/resources/formats/revealjs/quarto.scss` | RevealJS callout styles |
| `src/resources/formats/html/styles-callout.html` | Standalone HTML template |
| `src/resources/formats/dashboard/quarto-dashboard.scss` | Dashboard margin overrides |

### Lua Filters

| File | Purpose |
|------|---------|
| `src/resources/filters/customnodes/callout.lua` | Renderer selection and AST processing |
| `src/resources/filters/modules/callouts.lua` | Bootstrap renderer implementation |
| `src/resources/filters/common/pandoc.lua` | `hasBootstrap()` function |

### Tests

| File | Purpose |
|------|---------|
| `tests/docs/callouts.qmd` | All callout types and appearances |
| `tests/docs/playwright/html/callouts/` | Playwright test documents |
| `tests/integration/playwright/tests/html-callouts.spec.ts` | Playwright CSS tests |
1 change: 1 addition & 0 deletions news/changelog-1.9.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ All changes included in 1.9:
- ([#11929](https://github.com/quarto-dev/quarto-cli/issues/11929)): Import all `brand.typography.fonts` in CSS, whether or not fonts are referenced by typography elements.
- ([#13413](https://github.com/quarto-dev/quarto-cli/issues/13413)): Fix uncentered play button in `video` shortcodes from cross-reference divs. (author: @bruvellu)
- ([#13508](https://github.com/quarto-dev/quarto-cli/issues/13508)): Add `aria-label` support to `video` shortcode for improved accessibility.
- ([#13883](https://github.com/quarto-dev/quarto-cli/issues/13883)): Fix unequal top/bottom spacing in simple untitled callouts.

### `typst`

Expand Down
5 changes: 5 additions & 0 deletions src/resources/formats/html/bootstrap/_bootstrap-rules.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1498,6 +1498,11 @@ kbd,
margin-bottom: 0.2rem;
}

.callout.callout-style-simple:not(.callout-titled) .callout-body > :last-child,
.callout.callout-style-simple:not(.callout-titled) .callout-body > div > :last-child {
margin-bottom: 0.5em;
}

$code-block-border-left-color: $table-border-color !default;

.callout.callout-style-simple .callout-icon::before,
Expand Down
29 changes: 29 additions & 0 deletions tests/docs/playwright/html/callouts/callout-spacing.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
title: Callout Spacing Test
---

## Simple Untitled {#simple-untitled}

::: {.callout-note appearance="simple"}
Content without title to test spacing symmetry.
:::

## Simple Titled {#simple-titled}

::: {.callout-note appearance="simple"}
## Note Title
Content with title.
:::

## Default Untitled {#default-untitled}

::: {.callout-note appearance="default"}
Content without title in default appearance.
:::

## Default Titled {#default-titled}

::: {.callout-note appearance="default"}
## Note Title
Content with title in default appearance.
:::
39 changes: 39 additions & 0 deletions tests/integration/playwright/tests/html-callouts.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { test, expect } from '@playwright/test';
import { getCSSProperty } from '../src/utils';

test('Simple untitled callout has symmetric spacing', async ({ page }) => {
await page.goto('./html/callouts/callout-spacing.html');

// Simple untitled callout structure:
// .callout-style-simple > .callout-body > .callout-body-container > p
const simpleUntitledSection = page.locator('#simple-untitled');
const lastChild = simpleUntitledSection.locator('.callout-style-simple:not(.callout-titled) .callout-body-container > p:last-child');

// Verify margin-bottom > 0 (compensation for -0.4em body margin is applied)
const marginBottom = await getCSSProperty(lastChild, 'margin-bottom', true) as number;
expect(marginBottom).toBeGreaterThan(0);
});

test('Simple titled callout spacing is handled by padding', async ({ page }) => {
await page.goto('./html/callouts/callout-spacing.html');

// Simple titled callout structure:
// .callout-style-simple.callout-titled > .callout-body-container.callout-body > p
const simpleTitledSection = page.locator('#simple-titled');
const lastChild = simpleTitledSection.locator('.callout-style-simple.callout-titled .callout-body > p:last-child');

const paddingBottom = await getCSSProperty(lastChild, 'padding-bottom', true) as number;
expect(paddingBottom).toBeGreaterThan(0);
});

test('Default callout spacing is handled by padding', async ({ page }) => {
await page.goto('./html/callouts/callout-spacing.html');

// Default callouts always have .callout-titled class (even without explicit title)
// Structure: .callout-style-default.callout-titled > .callout-body > p
const defaultSection = page.locator('#default-untitled');
const lastChild = defaultSection.locator('.callout-style-default .callout-body > p:last-child');

const paddingBottom = await getCSSProperty(lastChild, 'padding-bottom', true) as number;
expect(paddingBottom).toBeGreaterThan(0);
});
Loading