Skip to content
Merged
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
10 changes: 10 additions & 0 deletions .claude/rules/quarto-web-workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@

Always PR to `main` first for shared content. Never PR directly to `prerelease` for changes that apply to both stable and prerelease.

## `_freeze/` Updates

Any source change to a `.qmd` that has a `_freeze/` entry — even non-executable content like text or includes — invalidates the freeze hash. A stale hash causes the deploy preview to show the old cached page.

**Wrong reasoning to avoid:** "I only added markdown, not executable code, so no `_freeze/` update needed." The cache is per-page, not per-cell.

**Before committing:** `git diff HEAD -- _freeze/` — every edited frozen `.qmd` must have a corresponding `_freeze/` change. (Use `HEAD` to show both staged and unstaged changes.)

**If the freeze-check hook blocks a commit or push:** run `quarto render <file.qmd>` then commit the updated `_freeze/` output alongside the source change.

## Avoid duplicating doc content

When a plan touches 3+ pages with similar prose, **scan for an existing shared-partial / include pattern in the repo before locking duplication into the plan.** quarto-web uses Quarto's `{{< include _foo.md >}}` shortcode extensively — examples in `docs/computations/` (e.g. `_jupyter-rendering.md`, `_jupyter-install.md`, `_caching-more.md`), `docs/tools/` (`_chunk-options.md`), and `docs/get-started/authoring/` (`_text-editor.md`). Many partials pair `{{< include >}}` with `.content-visible when-meta="..."` so the same source renders differently per consuming page.
Expand Down
15 changes: 15 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash .githooks/claude-hook.sh"
}
]
}
]
}
}
11 changes: 11 additions & 0 deletions .githooks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Git Hooks

This directory contains project git hooks. Activate with:

```bash
git config core.hooksPath .githooks
```

## Hooks

**`pre-commit`** — Blocks commits where a `.qmd` file with a `_freeze/` entry was edited but `_freeze/` was not updated. Fix: `quarto render <file.qmd>`, then commit the updated `_freeze/` output alongside the source.
72 changes: 72 additions & 0 deletions .githooks/check-freeze.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/bin/bash
# check-freeze.sh [commit|push]
#
# Checks that _freeze/ hash is current for any .qmd files being committed or pushed.
# Quarto's freeze hash is the MD5 of the source file (LF-normalized).
# Any content change to a page with executable code requires a _freeze/ update.
#
# Compatible with bash 3.2+ (macOS system bash), Linux, and Windows Git Bash.

# Cross-platform MD5 of stdin, LF-normalized (strips \r before hashing)
md5_lf() {
tr -d '\r' | (
if command -v md5sum >/dev/null 2>&1; then
md5sum | cut -d' ' -f1
else
md5 -r | cut -d' ' -f1
fi
)
}

MODE="${1:-commit}"
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0
STALE_FILE=$(mktemp)
trap 'rm -f "$STALE_FILE"' EXIT

if [ "$MODE" = "commit" ]; then
QMDS=$(git diff --cached --name-only 2>/dev/null | grep '\.qmd$' || true)
elif [ "$MODE" = "push" ]; then
REMOTE=$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null) || exit 0
[ -n "$REMOTE" ] || exit 0
QMDS=$(git diff "${REMOTE}..HEAD" --name-only 2>/dev/null | grep '\.qmd$' || true)
else
exit 0
fi

[ -n "$QMDS" ] || exit 0

while IFS= read -r qmd; do
[ -n "$qmd" ] || continue
freeze_rel="_freeze/${qmd%.qmd}/execute-results/html.json"

# Read freeze JSON from git objects, not working tree, to avoid false passes
# when the freeze file is updated on disk but not yet staged.
if [ "$MODE" = "commit" ]; then
freeze_content=$(git show ":${freeze_rel}" 2>/dev/null || git show "HEAD:${freeze_rel}" 2>/dev/null)
else
freeze_content=$(git show "HEAD:${freeze_rel}" 2>/dev/null)
fi
[ -n "$freeze_content" ] || continue

stored_hash=$(printf '%s' "$freeze_content" | jq -r '.hash // empty' 2>/dev/null)
[ -n "$stored_hash" ] || continue

if [ "$MODE" = "commit" ]; then
current_hash=$(git show ":$qmd" 2>/dev/null | md5_lf)
else
current_hash=$(git show "HEAD:$qmd" 2>/dev/null | md5_lf)
fi

[ -n "$current_hash" ] || continue
[ "$current_hash" = "$stored_hash" ] || echo "$qmd" >> "$STALE_FILE"
done <<< "$QMDS"

if [ -s "$STALE_FILE" ]; then
echo "_freeze/ not updated for:" >&2
sed 's/^/ /' "$STALE_FILE" >&2
echo "" >&2
echo "Run: quarto render <file.qmd> then commit the updated _freeze/ output." >&2
exit 2
fi

exit 0
15 changes: 15 additions & 0 deletions .githooks/claude-hook.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash
# Claude Code PreToolUse hook: check _freeze/ before git commit or push.
# Reads tool input JSON from stdin, routes to check-freeze.sh.

COMMAND=$(jq -r '.tool_input.command // ""' 2>/dev/null)

HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"

if echo "$COMMAND" | grep -qE 'git[[:space:]]+commit'; then
exec bash "$HOOK_DIR/check-freeze.sh" commit
elif echo "$COMMAND" | grep -qE 'git[[:space:]]+push'; then
exec bash "$HOOK_DIR/check-freeze.sh" push
fi

exit 0
6 changes: 6 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash
# Git pre-commit hook: verify _freeze/ is current for staged .qmd files.
# Setup: git config core.hooksPath .githooks

HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"
exec bash "$HOOK_DIR/check-freeze.sh" commit

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/authoring/_visibility-vs-execution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The `.content-visible` and `.content-hidden` classes control whether content is *shown* in the rendered output. They do **not** prevent the code in cells they wrap from executing. If you need to skip execution entirely based on the output format — for example, to avoid loading a package or calling a service that only makes sense for one format — have the cell itself read the `QUARTO_EXECUTE_INFO` environment variable. See [Execution Context Information](/docs/advanced/quarto-execute-info.qmd) for the JSON schema and examples in R, Python, and Julia.
6 changes: 5 additions & 1 deletion docs/authoring/conditional.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,8 @@ path:
````

This feature is often useful alongside [project profiles](/docs/projects/profiles.qmd).
Different profiles can set different metadata values, and so can control the metadata used in conditional content.
Different profiles can set different metadata values, and so can control the metadata used in conditional content.

## Conditional Execution

{{< include _visibility-vs-execution.md >}}
5 changes: 5 additions & 0 deletions docs/authoring/cross-references-divs.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -438,3 +438,8 @@ Scatterplot

@fig-scatterplot
````

{{< include _visibility-vs-execution.md >}}

When you want visibility to depend on something beyond output format — for example, the same document rendered as a draft vs. a final version, or for an internal vs. an external audience — combine `.content-visible`/`.content-hidden` with the `when-meta` attribute.
You can then control visibility with values you set in your metadata, either in your document header, or via [project profiles](/docs/projects/profiles.qmd).
8 changes: 8 additions & 0 deletions docs/computations/execution-options.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -498,3 +498,11 @@ echo "foo"
````

Note that the Knitr engine also supports ```` ```{python} ```` cells, enabling the combination of R, Python, and Bash in the same document

## Conditional Execution

In some cases you want code execution itself — not just visibility of its output — to depend on the active output format. For example, when rendering to PDF you may want to skip a cell that loads an interactive HTML widget, rather than execute it and hide the result.

Quarto exposes the current rendering context in the `QUARTO_EXECUTE_INFO` environment variable. Cells can read this variable to detect the active format and branch accordingly. See [Execution Context Information](/docs/advanced/quarto-execute-info.qmd) for the full JSON schema and examples in R, Python, and Julia.

This complements format-based [conditional content](/docs/authoring/conditional.qmd), which controls visibility of *already-executed* output. `QUARTO_EXECUTE_INFO` lets you skip work, choose a different code path, or load a different library before any output is produced.
39 changes: 39 additions & 0 deletions docs/computations/julia.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,26 @@ Please direct questions and requests regarding this functionality to the
[QuartoNotebookRunner](https://github.com/PumasAI/QuartoNotebookRunner.jl)
repository.

## Conditional Execution

To execute a cell only for certain output formats, read the [`QUARTO_EXECUTE_INFO`](/docs/advanced/quarto-execute-info.qmd) environment variable inside the cell.
For example, you could execute code only if the target format is based on HTML (e.g. for `html`, `revealjs`, and `dashboard` formats):

```` markdown
```{{julia}}
using JSON

info = JSON.parsefile(ENV["QUARTO_EXECUTE_INFO"])
is_html = info["format"]["identifier"]["base-format"] == "html"

if is_html
# HTML-only code, e.g. an interactive widget
end
```
````

This pairs well with [conditional content](/docs/authoring/conditional.qmd) when both visibility and execution should depend on format. See [Execution Context Information](/docs/advanced/quarto-execute-info.qmd) for the full JSON schema.

# Using the `jupyter` engine

### Installation {#installation}
Expand Down Expand Up @@ -662,3 +682,22 @@ To use Jupyter Cache you'll want to first install the `jupyter-cache` package:

To enable the cache for a document, add the `cache` option. For example:

## Conditional Execution

To execute a cell only for certain output formats, read the [`QUARTO_EXECUTE_INFO`](/docs/advanced/quarto-execute-info.qmd) environment variable inside the cell.
For example, you could execute code only if the target format is based on HTML (e.g. for `html`, `revealjs`, and `dashboard` formats):

```` markdown
```{{julia}}
using JSON

info = JSON.parsefile(ENV["QUARTO_EXECUTE_INFO"])
is_html = info["format"]["identifier"]["base-format"] == "html"

if is_html
# HTML-only code, e.g. an interactive widget
end
```
````

This pairs well with [conditional content](/docs/authoring/conditional.qmd) when both visibility and execution should depend on format. See [Execution Context Information](/docs/advanced/quarto-execute-info.qmd) for the full JSON schema.
23 changes: 23 additions & 0 deletions docs/computations/python.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,26 @@ Note that this step is not required if you are merely using conda with Quarto. I
:::

{{< include _jupyter-daemon.md >}}

## Conditional Execution

To execute a cell only for certain output formats, read the [`QUARTO_EXECUTE_INFO`](/docs/advanced/quarto-execute-info.qmd) environment variable inside the cell.
For example, you could execute code only if the target format is based on HTML (e.g. for `html`, `revealjs`, and `dashboard` formats):

```` markdown
```{{python}}
import json
import os

with open(os.environ["QUARTO_EXECUTE_INFO"]) as f:
info = json.load(f)

is_html = info["format"]["identifier"]["base-format"] == "html"

if is_html:
# HTML-only code, e.g. an interactive widget
...
```
````

This pairs well with [conditional content](/docs/authoring/conditional.qmd) when both visibility and execution should depend on format. See [Execution Context Information](/docs/advanced/quarto-execute-info.qmd) for the full JSON schema.
20 changes: 20 additions & 0 deletions docs/computations/r.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,26 @@ airquality$TempC <- (5 / 9) * (airquality$Temp - 32)

Unless you want to specify a cross-reference avoid using the [reserved cross-reference prefixes](/docs/authoring/cross-references.qmd#reserved-prefixes) for chunk labels.

## Conditional Execution

To execute a cell only for certain output formats, read the [`QUARTO_EXECUTE_INFO`](/docs/advanced/quarto-execute-info.qmd) environment variable inside the cell.
For example, you could execute code only if the target format is based on HTML (e.g. for `html`, `revealjs`, and `dashboard` formats):

```` markdown
```{{r}}
info <- jsonlite::fromJSON(Sys.getenv("QUARTO_EXECUTE_INFO"))
is_html <- info$format$identifier$`base-format` == "html"

if (is_html) {
# HTML-only code, e.g. an interactive widget
}
```
````

This pairs well with [conditional content](/docs/authoring/conditional.qmd) when both visibility and execution should depend on format. See [Execution Context Information](/docs/advanced/quarto-execute-info.qmd) for the full JSON schema.

For R cells using the knitr engine, knitr's `knitr::is_html_output()`, `knitr::is_latex_output()`, and `knitr::pandoc_to()` predicates are also available for the common format-detection cases. See the [knitr manual](https://pkg.yihui.org/knitr/manual#sec:man-output_type).

## Output Formats {#output-formats}

Another difference between R Markdown and Quarto is related to output formats. Quarto includes many more built in output formats (and many more options for customizing each format). Quarto also has native features for special project types like [Websites](/docs/websites/), [Books](/docs/books/), and [Blogs](/docs/websites/website-blog.qmd) (rather than relying on external packages).
Expand Down