diff --git a/claude-notes/plans/2026-08-14-format-css-not-copied.md b/claude-notes/plans/2026-08-14-format-css-not-copied.md
new file mode 100644
index 000000000..5859dbd09
--- /dev/null
+++ b/claude-notes/plans/2026-08-14-format-css-not-copied.md
@@ -0,0 +1,487 @@
+# format.html.css files are neither copied into the site nor rebased per page (bd-format-css-not-copied-crn3bjdz)
+
+**Date:** 2026-08-14
+**Braid:** bd-format-css-not-copied-crn3bjdz (bug, p1, label `websites`)
+**Checkout:** main checkout, branch `main` @ `10d86829` (investigation only — no worktree/branch created)
+**Status:** Implementation complete on branch
+`braid/bd-format-css-not-copied-crn3bjdz` (commits `37758160` +
+`86a6f79d` on top of the investigation commits). All phases done; full
+`cargo xtask verify` green; preview verified in a real browser (false
+Q-5-29 fixed, css-application gap filed as bd-b3oq2fsy). Awaiting
+push/PR approval.
+
+## Phase 4 evidence (end-to-end, real binary)
+
+Run 2026-08-14 on the implementation branch:
+
+```
+cargo run --bin q2 -- render claude-notes/plans/format-css-not-copied-investigation/repro
+# → "Rendered 2 of 2 files", exit 0
+```
+
+Output inspected directly (matches Q1's expected table from the repro
+README exactly):
+
+- `_site/styles.css` exists (marker property `--repro-project-css` present);
+- `_site/site_libs/quarto-contrib/quarto-project/acme/widget/widget.css`
+ exists (marker `--repro-extension-css` present); no `_site/_extensions/`;
+- `index.html`: `href="styles.css"`,
+ `href="site_libs/quarto-contrib/quarto-project/acme/widget/widget.css"`,
+ linked after the theme bundle;
+- `deep/deeper/index.html`: `href="../../styles.css"`,
+ `href="../../site_libs/quarto-contrib/quarto-project/acme/widget/widget.css"`.
+
+Also verified through the binary: single-doc `fancyfmt-html` extension
+render relocates+copies bundled css to
+`test_files/quarto-contrib/quarto-project/fancyfmt/fmt-style.css` (its
+smoke-all fixture now pins this — previously the link only worked because
+output landed beside the source tree).
+
+## Implementation shape (as landed)
+
+- **Marking** (`crates/quarto-core/src/project/format_css.rs`):
+ `mark_css_path_values` — existence-driven, per layer, called from
+ `MetadataMergeStage` for the project layer (after the `!path`
+ adjustment; diagnostics dropped there), directory-metadata layers, and
+ the document layer (diagnostics pushed per document).
+ `missing_project_css_diagnostics` runs once per project render from
+ `ProjectPipeline::run` into `project_diagnostics` (Q-5-29).
+- **Transform** (`crates/quarto-core/src/transforms/format_css.rs`):
+ `FormatCssTransform` (Normalization phase, self-gated to HTML-family,
+ no-op in VFS mode) — consumes only marked Path entries; mirrors
+ project-relative paths; relocates `_extensions/**` to
+ `quarto-contrib/quarto-project/**` via `ArtifactScope::Project`
+ resolver queries; pushes `ResourceCopyIntent`s (skipping src==dest);
+ rewrites entries to per-page hrefs. Never diagnoses.
+- **Revealjs**: `apply_template.rs` reveal branch appends
+ `user_css_urls(&metadata)` after the vendored deck assets.
+- **Q-5-29**: catalog entry + `docs/errors/project/Q-5-29.qmd`.
+
+## Triage verdict
+
+**Ready to design.** The symptom reproduces at HEAD, both halves of the fix
+have direct in-tree precedent (favicon copy + Path-kind metadata rebase /
+resource-resolver href resolution), and the remaining decisions are genuine
+design choices (output layout, which rebase mechanism, scope), not missing
+information.
+
+## Issue context
+
+Filed 2026-08-14 by the q2-connect-docs porting session (origin strand in that
+skein: br-format-css-not-copied-4jnxbq38). A website project declaring
+
+```yaml
+format:
+ html:
+ css:
+ - styles.css
+ - _extensions/acme/widget/widget.css
+```
+
+gets a `` to each file on every page, but:
+
+1. **Not copied** — neither file is written into `_site/`, so every link
+ 404s. No diagnostic; exit 0. Q1 copies both (project css to
+ `_site/styles.css`; extension-owned css to
+ `site_libs/quarto-contrib/quarto-project/acme/widget/widget.css`).
+2. **Not rebased** — the href is emitted verbatim at every depth
+ (`styles.css` on `deep/deeper/index.html`, where Q1 emits
+ `../../styles.css`). The built-in theme stylesheets on the same page *are*
+ rebased — that asymmetry is the control.
+
+Real-world impact: all 352 rendered pages of the Posit Connect docs port link
+two nonexistent stylesheets (704 broken references). Invisible to text-diff
+sweeps because CSS contributes no text.
+
+Not the same as bd-of20unsb (extension `contributes.formats` fragment paths,
+fixed in 0.21.0): here the paths live in the project's own `_quarto.yml`; the
+second one merely points into `_extensions/`. Notably, the repro's
+`_extensions/acme/widget/` contains **only** `widget.css` — no
+`_extension.yml` — so no extension machinery is involved at all.
+
+## Dependency graph
+
+**Empty** — no edges in the skein (strand is hours old). Context instead
+comes from the strands the description references:
+
+- **bd-root-relative-paths-design-fc5pvkcv** (in_progress, design) — the
+ navbar-logo/root-absolute-path design session. Its Decision 5 ("favicon is
+ not special — config-declared assets q2 knows about get the same
+ warn-and-continue copy treatment") is the stated policy this bug falls
+ under. Its case-A fix (0.21.0) built `copy_navbar_logo` /
+ `copy_footer_images` on the shared `copy_asset_file` helper — the exact
+ seam to extend.
+- **bd-of20unsb** (in_progress; fix shipped in 0.21.0 per repro README) —
+ extension-fragment path rebasing. Its mechanism (mark values as
+ `ConfigValueKind::Path`, existence-driven, then let the metadata merge
+ rebase them per document) is one of the two candidate mechanisms for the
+ rebase half here.
+
+## What the code looks like today
+
+All paths verified at `main` @ `10d86829`:
+
+- **Link emission**: `extract_css_from_meta`
+ (`crates/quarto-core/src/template.rs:928`) reads the `css` metadata key
+ (scalar / PandocInlines / array) and appends the strings **verbatim** to
+ the template `css` list (`render_with_compiled_template`,
+ `template.rs:699-709`). No resolver, no copy, no existence check.
+- **Why theme css *is* rebased**: built-in stylesheets are artifacts;
+ `ApplyTemplateStage` computes their URLs via the per-page
+ `ResourceResolverContext` (`apply_template.rs:166`,
+ `collect_artifact_urls`). User css never touches that path.
+- **Copy boundary**: `crates/quarto-core/src/project/website_post_render.rs`
+ has `copy_favicon`, `copy_navbar_logo`, `copy_footer_images`, all sharing
+ `copy_asset_file` and the warn-on-missing-source pattern. `format.html.css`
+ has no counterpart. (All native-only; the in-browser preview has no on-disk
+ output dir.)
+- **Href precedent (per-page transform)**: `WebsiteFaviconTransform`
+ (`transforms/website_favicon.rs`) resolves a page-relative href through
+ `ctx.resource_resolver` and appends the `` to
+ `rendered.includes.header`.
+- **Rebase precedent (metadata merge)**: `FRAGMENT_PATH_PATTERNS`
+ (`project/mod.rs:696`) already lists `["format", "*", "css"]` — but only
+ for **extension** `contributes.project` fragments. Values marked
+ `ConfigValueKind::Path` are rebased project-root → document-dir by
+ `adjust_paths_to_document_dir` during the metadata merge
+ (`metadata_merge.rs:256` applies it to the project-config layer). The
+ project's own `_quarto.yml` values are never *marked* Path-kind, so the
+ machinery never fires for them.
+- **Resource collector**: `resource_collector.rs` walks the AST only;
+ metadata-declared css is invisible to it (same blind spot the design
+ strand documents for raw HTML).
+
+### Repro at HEAD
+
+Fixture: `claude-notes/plans/format-css-not-copied-investigation/repro/`
+(mirrors the external repro at
+`~/repos/github/cscheid/q2-connect-docs/llms-info/repros/format-css-not-copied/`).
+
+Run 2026-08-14 at `main` @ `10d86829` (pre-flight `cargo xtask verify
+--skip-hub-build` green, 12167/12167):
+
+```
+cargo run --bin q2 -- render claude-notes/plans/format-css-not-copied-investigation/repro
+# → "Rendered 2 of 2 files", exit 0, no diagnostic
+```
+
+Observed output (inspected directly):
+
+- `_site/styles.css`: **does not exist**; no css file anywhere in `_site`
+ besides `site_libs/` assets. Marker custom properties absent from the
+ theme bundle — the declared css is dropped entirely.
+- `_site/index.html` links: `site_libs/…` (fine), then verbatim
+ `href="styles.css"` and `href="_extensions/acme/widget/widget.css"` —
+ both 404.
+- `_site/deep/deeper/index.html`: `../../site_libs/…` (rebased correctly)
+ immediately beside verbatim `href="styles.css"` /
+ `href="_extensions/acme/widget/widget.css"` — the asymmetry the strand
+ describes, confirmed on one page.
+
+Both defects confirmed; matches the external repro's table for q2 0.21.0.
+
+## Evidence pass 1: how and why Q1 organizes the output (external-sources/quarto-cli)
+
+Two separate machineries, plus a website-only patch; `css:` straddles them.
+
+**Rebase (metadata-time, not DOM-time).** Q1 runs pandoc with
+`cwd = dirname(input)` (`src/command/render/pandoc.ts:371-372`), so every
+relative path in the defaults file must be input-dir-relative. Project-config
+paths are authored project-relative, so Q1 rewrites them at metadata-merge
+time: `toInputRelativePaths` (`src/project/project-shared.ts:138-207`), called
+from `projectMetadataForInputFile` (`render-contexts.ts:741-757`). It is a
+blind recursive walk over the whole merged config: any non-absolute string
+that names an existing file under the project dir becomes
+`/` (`offset = relative(inputDir, baseDir)`) — that is the
+entire story of `../../styles.css`. **Existence-driven, layer-aware (the
+same function runs per layer: project config, `_metadata.yml` with the
+metadata file's dir, extension config with the extension dir as base).**
+The HTML template then emits the string verbatim
+(`resources/formats/html/pandoc/html.template:23-24`). The deno-dom
+postprocessor never rewrites a plain relative `css` href — it only records
+it as a resource ref.
+
+**Copy (resource-ref mirror).** The website HTML postprocessor collects every
+resource-tag href; refs are resolved to absolute paths and mirrored:
+`join(formatOutputDir, relative(projDir, file))`
+(`src/command/render/project.ts:735-766`). So project-root css lands at
+`_site/styles.css` purely because resource copying is a project-relative
+mirror. `copyResourceFile` also chases `url()`/`@import` inside copied css
+(`project-resources.ts:110-138`).
+
+**The `_extensions` relocation (website/book-only).**
+`projectExtensionPathResolver` (`src/extension/extension.ts:158-189`) is a
+resolver injected into the website postprocessor: an href resolving under
+`_extensions/` is copied to
+`/quarto-contrib/quarto-project/`
+and the DOM attribute rewritten. Rationale, from the code and introducing
+commits (`364eb9a2d`, `b1e866553`):
+
+1. Underscore-prefixed dirs are systematically non-output
+ (`projectHiddenIgnoreGlob`, `project-context.ts:878-886` — the same list
+ that excludes `README.*`, `CLAUDE.md`, `AGENTS.md`). Publishing
+ `_site/_extensions/` would leak Lua sources, `_extension.yml`, READMEs;
+ relocating means only individually-referenced files ship. This matches
+ the user's stated constraint exactly.
+2. The lib dir is freezer-managed and pruned by a whitelist of known names
+ (`formatLibDirs`, `project-default.ts:23-29`); `quarto-contrib` is the
+ single reserved namespace that protects third-party content without
+ enumerating extension names. `quarto-project` is a fixed literal meaning
+ "the project itself is the contributor" (used when a file is referenced
+ by raw path rather than through a named HTML dependency). There is no
+ per-org namespace logic — the `_extensions` prefix is stripped and the
+ remainder (org segment included) preserved verbatim.
+
+**Book = website machinery** (`book.ts:74` inherits, `book.ts:225-243`
+merges website formatExtras), so the relocation applies there too.
+Single-doc renders never rebase (no project offset) and never relocate;
+lib assets go to `_files/libs/`. Notably, a *default*-type project
+with an `output-dir` mirrors `_extensions/...` css into the output
+verbatim in Q1 — the relocation is deliberately a website/book behavior.
+
+**Other keys (Q1):** `include-*` files are inlined by pandoc (never
+copied); `format-resources` are flattened **basename-only** next to the
+output — they exist for writers that want sibling files (LaTeX `.cls`,
+Typst), not for ``s; `theme`/SCSS compiles to a cache then ships as a
+FormatDependency under `site_libs/bootstrap|quarto-html` with hrefs built
+from the input-relative lib dir; document `resources:`/`project.resources`
+mirror project-relative paths, no hrefs. Four placement policies for four
+consumption models.
+
+## Evidence pass 2: q2 boundary audit
+
+Per-key status at `main` @ `10d86829` (file:line cites in the agent record;
+load-bearing ones inline):
+
+| key | q2 today |
+|---|---|
+| `format.html.css` | linked verbatim (`extract_css_from_meta`, `template.rs:928`); never copied; only other references are the two extension pattern tables. **On revealjs, user css is dropped entirely** — the scaffold only takes artifact URLs (`revealjs/assemble.rs:369-410`, `apply_template.rs:302-306`), so not even a broken `` is emitted. |
+| `theme` | compiled to Project-scoped artifact `css:theme:` (`compile_theme_css.rs:707-730`); per-page URL via resolver (`apply_template.rs:166` → `html_url_for`); flushed to `site_libs` (website) or per-page `_files` (default/book). Healthy. |
+| `include-in-header`/`-before-body`/`-after-body` | inlined by `IncludeResolveStage` into `rendered.includes.*` — Q1 parity. Raw `