Skip to content

Commit e8fe22f

Browse files
cdervclaude
andauthored
Fix unequal spacing in simple untitled callouts (#13934)
* Fix unequal spacing in simple untitled callouts Simple callouts have a -0.4em bottom margin on .callout-body for collapsed/contentless callouts. For untitled simple callouts, this creates asymmetric spacing since only 0.2rem compensation exists. Add scoped rule for simple untitled callouts to set 0.5em bottom margin on last-child, matching the top margin compensation. * docs: Add HTML callout styling architecture guide Document the three-tier callout styling system (Bootstrap, RevealJS, Standalone/EPUB) for LLM reference when working on callout-related issues. Add a CLAUDE.md to help claude code write these docs Co-Authored-By: Claude Opus 4.5 <[email protected]> * test: Add Playwright tests for callout spacing Test symmetric spacing in simple untitled callouts and verify titled/default callouts use padding compensation correctly. Co-Authored-By: Claude Opus 4.5 <[email protected]> * docs: Fix date typo and add date-checking guidance Address review feedback from PR #13934: - Fix 2025 → 2026 date typo in callout-styling-html.md - Add guidance to verify dates from system environment Co-Authored-By: Claude Opus 4.5 <[email protected]> --------- Co-authored-by: Claude Opus 4.5 <[email protected]>
2 parents eef0f39 + 8addfb4 commit e8fe22f

6 files changed

Lines changed: 354 additions & 0 deletions

File tree

llm-docs/CLAUDE.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# LLM Documentation
2+
3+
This directory contains documentation written for LLM assistants working on the Quarto codebase.
4+
5+
## Staleness Check
6+
7+
Each document has YAML frontmatter with analysis metadata:
8+
9+
```yaml
10+
---
11+
main_commit: abc1234 # merge-base with main (stable reference)
12+
analyzed_date: 2025-01-22
13+
key_files:
14+
- path/to/file1.ts
15+
- path/to/file2.lua
16+
---
17+
```
18+
19+
**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.
20+
21+
**Before relying on a document**, check if key files have changed:
22+
23+
```bash
24+
git log --oneline <main_commit>..main -- <key_files>
25+
```
26+
27+
If there are significant changes, re-explore the codebase and update the document.
28+
29+
## Updating Documents
30+
31+
After re-analyzing, update the frontmatter:
32+
33+
```bash
34+
# Get merge-base with main (use upstream/main if that's the main remote)
35+
git merge-base HEAD main | cut -c1-9
36+
```
37+
38+
Then update `main_commit`, `analyzed_date`, and verify `key_files` list is complete.
39+
40+
**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.
41+
42+
## Document Purpose
43+
44+
These docs capture architectural understanding that would otherwise require extensive codebase exploration. They are NOT:
45+
- User documentation (that's at quarto.org)
46+
- Code comments (those live in source files)
47+
- Issue-specific notes (those go in PR descriptions)
48+
49+
They ARE:
50+
- Architectural overviews for AI assistants
51+
- File location maps for common tasks
52+
- Pattern documentation for consistency

llm-docs/callout-styling-html.md

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
---
2+
main_commit: 50db5a5b0
3+
analyzed_date: 2026-01-22
4+
key_files:
5+
- src/resources/formats/html/bootstrap/_bootstrap-rules.scss
6+
- src/resources/formats/revealjs/quarto.scss
7+
- src/resources/formats/html/styles-callout.html
8+
- src/resources/filters/customnodes/callout.lua
9+
---
10+
11+
# HTML Callout Styling Architecture
12+
13+
This document describes the CSS architecture for Quarto callouts across HTML-based output formats.
14+
15+
## Overview
16+
17+
Quarto uses a **three-tier callout styling architecture** depending on the output format:
18+
19+
| Tier | Formats | CSS Location | Features |
20+
|------|---------|--------------|----------|
21+
| Bootstrap HTML | `html` (with themes) | `formats/html/bootstrap/_bootstrap-rules.scss` | Full theming, collapsible, dark mode |
22+
| RevealJS | `revealjs` | `formats/revealjs/quarto.scss` | Presentation-specific scaling, slide-aware |
23+
| Standalone HTML | `epub`, `gfm`, plain html | `formats/html/styles-callout.html` | Inline CSS, no dependencies |
24+
25+
All HTML callouts support three **appearance** values:
26+
- **default**: Full-featured with colored header background
27+
- **simple**: Lightweight with left border only
28+
- **minimal**: Maps to simple with `icon=false`
29+
30+
## Format Detection (Lua Filter)
31+
32+
The Lua filter `src/resources/filters/customnodes/callout.lua` selects the appropriate renderer:
33+
34+
```
35+
Renderer selection order:
36+
1. hasBootstrap() → Bootstrap HTML renderer
37+
2. isEpubOutput() || isRevealJsOutput() → Simpler HTML structure
38+
3. isGfmOutput() → GitHub markdown alerts
39+
4. Default → BlockQuote fallback
40+
```
41+
42+
The `hasBootstrap()` function (in `filters/common/pandoc.lua`) checks the `has-bootstrap` parameter set by TypeScript during format initialization.
43+
44+
## HTML Structure by Format
45+
46+
### Bootstrap HTML
47+
48+
```html
49+
<div class="callout callout-style-default callout-note callout-titled">
50+
<div class="callout-header d-flex align-content-center">
51+
<div class="callout-icon-container"><i class='callout-icon'></i></div>
52+
<div class="callout-title-container flex-fill">Title</div>
53+
</div>
54+
<div class="callout-body-container">
55+
<div class="callout-body">Content</div>
56+
</div>
57+
</div>
58+
```
59+
60+
### EPUB/RevealJS HTML
61+
62+
```html
63+
<div class="callout callout-note callout-style-default">
64+
<div class="callout-body">
65+
<div class="callout-icon-container"><i class='callout-icon'></i></div>
66+
<div class="callout-title">Title</div>
67+
<div class="callout-content">Content</div>
68+
</div>
69+
</div>
70+
```
71+
72+
Note: Bootstrap uses `callout-body-container` wrapper and Bootstrap utility classes (`d-flex`, `flex-fill`). EPUB/RevealJS uses a flatter structure.
73+
74+
## Feature Comparison
75+
76+
| Feature | Bootstrap | RevealJS | Standalone |
77+
|---------|-----------|----------|------------|
78+
| Collapsible | Yes | No | No |
79+
| Icon type | SVG (dynamic color) | SVG (dynamic color) | PNG (base64) |
80+
| Theming | Full Bootstrap vars | Presentation vars | Fixed colors |
81+
| Dark mode | Yes | Slide background aware | No |
82+
| Font scaling | Responsive | Presentation-specific (0.7em) | Fixed (0.9rem) |
83+
84+
---
85+
86+
## Bootstrap HTML Styling
87+
88+
File: `src/resources/formats/html/bootstrap/_bootstrap-rules.scss`
89+
90+
### Callout States
91+
92+
| State | CSS Class | Description |
93+
|-------|-----------|-------------|
94+
| Titled | `.callout-titled` | Has a title/header |
95+
| Untitled | `:not(.callout-titled)` | Content only, no header |
96+
| Collapsed | `.callout-header.collapsed` | Collapsible, currently closed |
97+
| Empty content | `.callout-empty-content` | No body content |
98+
99+
### Styling Patterns
100+
101+
**Base callout:**
102+
```scss
103+
.callout {
104+
margin-top: $callout-margin-top;
105+
margin-bottom: $callout-margin-bottom;
106+
border-radius: $border-radius;
107+
}
108+
```
109+
110+
**Simple vs Default appearance:**
111+
- `.callout-style-simple`: Left border only, lighter styling
112+
- `.callout-style-default`: Full border, colored header background
113+
114+
**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.
115+
116+
### Theming Variables
117+
118+
Bootstrap callouts use SCSS variables (in `_bootstrap-variables.scss`):
119+
120+
```scss
121+
$callout-border-width: 0.4rem !default;
122+
$callout-border-scale: 0% !default;
123+
$callout-icon-scale: 10% !default;
124+
$callout-margin-top: 1.25rem !default;
125+
$callout-margin-bottom: 1.25rem !default;
126+
```
127+
128+
Colors are defined per callout type (note, warning, important, tip, caution) using Bootstrap's color functions.
129+
130+
---
131+
132+
## RevealJS Styling
133+
134+
File: `src/resources/formats/revealjs/quarto.scss`
135+
136+
### Presentation-Specific Adjustments
137+
138+
```scss
139+
// Variables
140+
$callout-border-width: 0.3rem;
141+
$callout-margin-top: 1rem;
142+
$callout-margin-bottom: 1rem;
143+
144+
// Font scaling for slide readability
145+
.reveal div.callout {
146+
font-size: 0.7em;
147+
}
148+
```
149+
150+
### Light/Dark Slide Awareness
151+
152+
RevealJS callouts adjust colors based on slide background using the `shift_to_dark` mixin:
153+
154+
```scss
155+
.has-dark-background div.callout-note {
156+
// Lighter colors for dark backgrounds
157+
}
158+
```
159+
160+
---
161+
162+
## Standalone/EPUB Styling
163+
164+
File: `src/resources/formats/html/styles-callout.html`
165+
166+
### Characteristics
167+
168+
- **Inline CSS** embedded in HTML template
169+
- **PNG icons** (base64-encoded) instead of SVG
170+
- **Fixed colors**: Uses hardcoded `#acacac`, `silver` borders
171+
- **No collapsible support**
172+
- **No theming** - works without Bootstrap or any CSS framework
173+
174+
### Key Selectors
175+
176+
```css
177+
.callout /* Base container */
178+
.callout.callout-style-simple /* Simple bordered style */
179+
.callout.callout-style-default /* Default style with header */
180+
.callout-title /* Title container */
181+
.callout-body /* Content container */
182+
.callout-icon::before /* Icon pseudo-element */
183+
```
184+
185+
---
186+
187+
## CSS Class Reference
188+
189+
Classes applied across all HTML formats:
190+
191+
| Class | Applied When | Purpose |
192+
|-------|--------------|---------|
193+
| `.callout` | Always | Base container |
194+
| `.callout-{type}` | Always | Type: note, warning, important, tip, caution |
195+
| `.callout-style-{appearance}` | Always | Style: default, simple |
196+
| `.callout-titled` | Has title | Structural indicator |
197+
| `.no-icon` | `icon=false` | Suppress icon |
198+
| `.callout-empty-content` | No body | Empty state (Bootstrap only) |
199+
200+
---
201+
202+
## Related Files
203+
204+
### CSS/SCSS
205+
206+
| File | Purpose |
207+
|------|---------|
208+
| `src/resources/formats/html/bootstrap/_bootstrap-rules.scss` | Bootstrap HTML callout styles |
209+
| `src/resources/formats/html/bootstrap/_bootstrap-variables.scss` | Bootstrap callout variables |
210+
| `src/resources/formats/revealjs/quarto.scss` | RevealJS callout styles |
211+
| `src/resources/formats/html/styles-callout.html` | Standalone HTML template |
212+
| `src/resources/formats/dashboard/quarto-dashboard.scss` | Dashboard margin overrides |
213+
214+
### Lua Filters
215+
216+
| File | Purpose |
217+
|------|---------|
218+
| `src/resources/filters/customnodes/callout.lua` | Renderer selection and AST processing |
219+
| `src/resources/filters/modules/callouts.lua` | Bootstrap renderer implementation |
220+
| `src/resources/filters/common/pandoc.lua` | `hasBootstrap()` function |
221+
222+
### Tests
223+
224+
| File | Purpose |
225+
|------|---------|
226+
| `tests/docs/callouts.qmd` | All callout types and appearances |
227+
| `tests/docs/playwright/html/callouts/` | Playwright test documents |
228+
| `tests/integration/playwright/tests/html-callouts.spec.ts` | Playwright CSS tests |

news/changelog-1.9.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ All changes included in 1.9:
3232
- ([#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.
3333
- ([#13413](https://github.com/quarto-dev/quarto-cli/issues/13413)): Fix uncentered play button in `video` shortcodes from cross-reference divs. (author: @bruvellu)
3434
- ([#13508](https://github.com/quarto-dev/quarto-cli/issues/13508)): Add `aria-label` support to `video` shortcode for improved accessibility.
35+
- ([#13883](https://github.com/quarto-dev/quarto-cli/issues/13883)): Fix unequal top/bottom spacing in simple untitled callouts.
3536

3637
### `typst`
3738

src/resources/formats/html/bootstrap/_bootstrap-rules.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1498,6 +1498,11 @@ kbd,
14981498
margin-bottom: 0.2rem;
14991499
}
15001500

1501+
.callout.callout-style-simple:not(.callout-titled) .callout-body > :last-child,
1502+
.callout.callout-style-simple:not(.callout-titled) .callout-body > div > :last-child {
1503+
margin-bottom: 0.5em;
1504+
}
1505+
15011506
$code-block-border-left-color: $table-border-color !default;
15021507

15031508
.callout.callout-style-simple .callout-icon::before,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
title: Callout Spacing Test
3+
---
4+
5+
## Simple Untitled {#simple-untitled}
6+
7+
::: {.callout-note appearance="simple"}
8+
Content without title to test spacing symmetry.
9+
:::
10+
11+
## Simple Titled {#simple-titled}
12+
13+
::: {.callout-note appearance="simple"}
14+
## Note Title
15+
Content with title.
16+
:::
17+
18+
## Default Untitled {#default-untitled}
19+
20+
::: {.callout-note appearance="default"}
21+
Content without title in default appearance.
22+
:::
23+
24+
## Default Titled {#default-titled}
25+
26+
::: {.callout-note appearance="default"}
27+
## Note Title
28+
Content with title in default appearance.
29+
:::
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { test, expect } from '@playwright/test';
2+
import { getCSSProperty } from '../src/utils';
3+
4+
test('Simple untitled callout has symmetric spacing', async ({ page }) => {
5+
await page.goto('./html/callouts/callout-spacing.html');
6+
7+
// Simple untitled callout structure:
8+
// .callout-style-simple > .callout-body > .callout-body-container > p
9+
const simpleUntitledSection = page.locator('#simple-untitled');
10+
const lastChild = simpleUntitledSection.locator('.callout-style-simple:not(.callout-titled) .callout-body-container > p:last-child');
11+
12+
// Verify margin-bottom > 0 (compensation for -0.4em body margin is applied)
13+
const marginBottom = await getCSSProperty(lastChild, 'margin-bottom', true) as number;
14+
expect(marginBottom).toBeGreaterThan(0);
15+
});
16+
17+
test('Simple titled callout spacing is handled by padding', async ({ page }) => {
18+
await page.goto('./html/callouts/callout-spacing.html');
19+
20+
// Simple titled callout structure:
21+
// .callout-style-simple.callout-titled > .callout-body-container.callout-body > p
22+
const simpleTitledSection = page.locator('#simple-titled');
23+
const lastChild = simpleTitledSection.locator('.callout-style-simple.callout-titled .callout-body > p:last-child');
24+
25+
const paddingBottom = await getCSSProperty(lastChild, 'padding-bottom', true) as number;
26+
expect(paddingBottom).toBeGreaterThan(0);
27+
});
28+
29+
test('Default callout spacing is handled by padding', async ({ page }) => {
30+
await page.goto('./html/callouts/callout-spacing.html');
31+
32+
// Default callouts always have .callout-titled class (even without explicit title)
33+
// Structure: .callout-style-default.callout-titled > .callout-body > p
34+
const defaultSection = page.locator('#default-untitled');
35+
const lastChild = defaultSection.locator('.callout-style-default .callout-body > p:last-child');
36+
37+
const paddingBottom = await getCSSProperty(lastChild, 'padding-bottom', true) as number;
38+
expect(paddingBottom).toBeGreaterThan(0);
39+
});

0 commit comments

Comments
 (0)