Project profiles: port from Quarto 1 (bd-fu16z22k) - #492
Merged
Conversation
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
Pure-logic module crates/quarto-core/src/project/project_profile.rs: QUARTO_PROFILE-string parsing (Q1 [ ,]+ split; edge-trim + dedup fix Q1's empty-name bug), strict name validation ([A-Za-z0-9][A-Za-z0-9._-]*), site-aware `profile:` extraction with stripping (BaseConfig / LocalConfig / Overlay), and the activation precedence chain (CLI > env > env-file > local default > base default, then profile.group first-member expansion appended after explicit selections). New error-catalog codes: Q-5-19 (unknown profile — emitted in Phase 1), Q-5-20 (invalid profile: shape), Q-5-21 (invalid profile name / empty selection), Q-5-22 (inert profile: key). TDD: 41 unit tests written first and observed failing on stubs. Full workspace suite green (11262 tests). Plan: claude-notes/plans/2026-08-10-project-profiles-port.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
During user-approved plan execution, commit at each clean phase boundary (checklist passed, workspace tests green) without stopping for approval; approval still required for ad-hoc commits, dirty states, and always for pushing. Decided in the bd-fu16z22k session. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ProjectContext::parse_config now resolves the project-profile activation set (explicit selection via new discover_with_profile, or QUARTO_PROFILE via runtime.env_get) and merges `_quarto-<name>.yml` overlays plus `_quarto.yml.local` into the project metadata before any field extraction — project.type, output-dir, render globs, resources, render scripts, and brand are all profile-aware. Overlays merge in reverse activation order (first-listed profile wins, Q1 parity); `_quarto.yml.local` is the highest-priority layer and also contributes profile.default. Diagnostics: Q-5-19 warning for an explicitly-selected profile that matches nothing; Q-5-22 for inert profile: keys in overlays / group-in-local; Q-5-20/21 shape and name errors abort discovery with spans. New ProjectConfig fields active_config_profiles (with provenance) and profile_config_paths, registered as bind_config_source candidates at every site (MetadataMergeStage, render/publish/preview script contexts, project resources, theme error candidates) so overlay-anchored diagnostics render real spans. TDD: 25 integration tests (project_profile_overlays.rs) written first and observed failing against a delegating stub. Full workspace suite green (11287 tests). Also this session: killed 2338 accumulated orphaned Jupyter kernels + 4932 stale connection files on the dev machine and filed bd-hxhnnlzs — a bare `cargo nextest run --workspace` reproducibly leaks ~15 orphan ipykernel processes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
--profile now works end-to-end on q2 render, get-config, and publish (threaded as data through every discover, including the post-pre-render-script and publish render-time re-discoveries; empty vec = flag absent, non-empty replaces QUARTO_PROFILE — Q1 parity). Preview keeps env-var activation only for now; the flag form needs HubContext threading (bd-pfgc273f). Project-less discovery also resolves and validates the selection, so bad names abort even without a _quarto.yml. Pass-1 cache key: active profile names (count-prefixed, order-sensitive) + overlay/local file bytes join Pass1KeyInputs; PROFILE_KEY_VERSION bumped to 2. A profile switch or overlay edit can no longer serve stale DocumentProfiles. Child processes see the normalized, group-expanded QUARTO_PROFILE unconditionally (the one exception to the real-env-wins rule from PR #486): engine subprocesses via EngineExecutionStage's env pairs, render scripts via RenderScriptsContext.quarto_profile, and {{< env QUARTO_PROFILE >}} via an EnvShortcodeHandler special case that beats the real environment. -v now echoes the active set with provenance. Two latent logging gaps fixed en route: verbose_to_filter directives never matched the q2 bin crate's tracing targets (quarto= does not prefix-match q2=), and the tracing fmt layer wrote to stdout instead of stderr. TDD: 14 binary-driven CLI tests written first (12 observed failing), plus cache-key, spawn-env, and shortcode unit tests. Full workspace green (11365 tests). E2E verified and recorded in the plan: overlay title in rendered HTML, env shortcode resolving "prod,draft", get-config divergence, -v echo. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fills the seam PR #486 left: _environment-<name> files now load for each active project profile (first-listed profile wins, matching the overlay rule), threaded from ProjectConfig.active_config_profiles through StageContext::new and subprocess_env_for_project. Q1's dotenv bootstrap is ported: QUARTO_PROFILE defined in _environment.local / _environment (never in profile variants — no activation recursion) selects profiles when neither --profile nor the real environment variable does, and loses to both. TDD: 6 binary-driven tests (3 observed failing first) covering bootstrap precedence, env-file layering through {{< env >}} in rendered HTML, and the no-recursion rule. Full workspace green (11371 tests). Closes bd-ev8mk1rp. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Greenfield port of Q1's content-hidden.lua as an AST transform: .content-visible / .content-hidden on divs, spans, and code blocks with when-format / unless-format, when-profile / unless-profile, and when-meta / unless-meta. Condition kinds AND together; comma/space-separated values within one condition OR (q2 extension — Q1's repeated-attribute OR is unrepresentable in q2's map-shaped Attr); unless-* negates; bare .content-hidden always hides; survivors keep classes but lose condition attributes. Runs FIRST in the Normalization phase, so hidden content disappears before callout assembly, shortcode resolution (no spurious warnings from excluded content), and crossref numbering (a hidden float consumes no number — tested). when-format reuses pampa's quarto.doc.is_format alias table (made pub) against the canonical lua format, so attributes and Lua filters can never disagree. when-meta does dotted-path lookup in the merged metadata with Q1 truthiness, which lets profile overlays drive content via config. Strictness: new Q-2-42 warning for unknown when-*/unless-* spellings and for elements carrying both marker classes (hidden wins); Q1 is silent on both. TDD: 15 binary-driven tests written first (14 observed failing) + 11 unit tests on the walker. Full workspace green (11397 tests). E2E verified both ways and recorded in the plan. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New user-facing docs page guides/projects/profiles.qmd (activation, overlay merging, profile.default/group, conditional content, code visibility, and a "Differences from Quarto 1" section); environment.qmd gains _environment-<profile> documentation (file table, precedence, secrets callout, no-recursion rule). Docs site rendered with q2 and inspected. smoke-all fixture metadata/project-profiles/: activation via profile.default so all three runners exercise overlays + when-profile + when-meta with no CLI flags — including the WASM runner (the feature's WASM-path coverage). Full cargo xtask verify (WASM leg included) green. Also verified with a real jupyter kernel: a python cell sees QUARTO_PROFILE=advanced,production under --profile. Strand bookkeeping: bd-mlj6 closed as implemented; deferred work filed as bd-pfgc273f (preview flag), bd-ip1lrgra (Lua quarto.project.profile), bd-kzwt3xcu (preview watch), bd-47hhbmaj (gitignore scaffolding), bd-spb7mobo (metadata-files decision). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cscheid
force-pushed
the
feature/bd-fu16z22k-project-profiles
branch
from
August 10, 2026 18:20
4e2eecf to
1d8262e
Compare
…h arg A test added on main after this branch diverged calls ShortcodeResolveTransform::with_lua_support with the pre-profiles 6-argument signature; pass quarto_profile: None like every other test caller. (Semantic merge conflict caught by PR CI.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cscheid
force-pushed
the
feature/bd-fu16z22k-project-profiles
branch
from
August 10, 2026 18:20
1d8262e to
bd4301c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ports Quarto 1's project profiles to Q2: activation via
--profile/QUARTO_PROFILE,profile.default+profile.group, config overlays (_quarto-<name>.yml), local overrides (_quarto.yml.local), profile environment files (_environment-<name>), and conditional content (when-/unless-×format/profile/meta) — built greenfield as an AST transform. Where Q1 guesses silently, Q2 validates with span-carrying diagnostics.Plan (design decisions, divergence table, per-phase records):
claude-notes/plans/2026-08-10-project-profiles-port.md. Strand: bd-fu16z22k.One phase per commit
ce502820project_profile.rs, catalog codes Q-5-19..22)117364cf_quarto-<name>.ymloverlays +_quarto.yml.local, FileId/span discipline at everybind_config_sourcesite5da7208e--profileon render/get-config/publish, pass-1 cache-key inputs, subprocessQUARTO_PROFILE,-vecho6e941e2e_environment-<profile>loading + dotenvQUARTO_PROFILEbootstrap (fills the seam PR #486 left)e107ff204e2eecfa(
9e0dc551is a small process-docs change decided mid-session: commit-and-continue at clean phase boundaries.)Design highlights
--profilereplacesQUARTO_PROFILE, which beats the_environment{,.local}bootstrap, which beatsprofile.defaultin_quarto.yml.local, then_quarto.yml; group expansion appends each group's first member after explicit selections. First-listed profile wins merge conflicts (consistent with the_environment-<p>layer order).ProjectConfig.active_config_profiles; child processes (engines, render scripts) get the normalized list viaCommand::envunconditionally — the one deliberate exception to the real-env-wins rule — and{{< env QUARTO_PROFILE >}}special-cases it.DocumentProfile(pass-1 summary) in q2; the new code consistently says project profiles /active_config_profiles, and the pass-1 cache key gained the project-profile inputs (PROFILE_KEY_VERSION→ 2) so a profile switch can't serve stale pass-1 results.profile:shape, Q-5-21 invalid names ([A-Za-z0-9][A-Za-z0-9._-]*, empty selections), Q-5-22 inertprofile:keys, Q-2-42 condition-attribute typos. Behavioral divergences (array concat instead of union-dedup; comma-OR in condition values) are documented in the docs page's "Differences from Quarto 1".--profileflag threading (bd-pfgc273f;QUARTO_PROFILE=x q2 previewworks today),metadata-filesdecision (bd-spb7mobo), Luaquarto.project.profile(bd-ip1lrgra), preview watch (bd-kzwt3xcu), gitignore scaffolding (bd-47hhbmaj).Test plan
TDD red-first throughout; ~90 new tests: 41 resolution unit tests, 25 overlay integration tests, 35 binary-driven CLI/e2e tests (comma/repeated flags, precedence, diagnostics through the binary, env-file layering via
{{< env >}}in rendered HTML, crossref renumbering under hidden floats), 11 conditional-content walker tests, cache-key domain-separation tests, a real-spawn script-env test, and a smoke-all fixture activated viaprofile.defaultso the native and WASM runners both exercise the stack.E2E verified with real invocations (recorded in the plan): overlay title in rendered HTML,
q2 get-configdivergence with/without--profile,-vprovenance echo, and a live jupyter kernel printingQUARTO_PROFILE=advanced,production.Gates:
cargo nextest run --workspacegreen (11,397 tests), fullcargo xtask verify(WASM leg included) green, clippy/fmt/cargo xtask lintclean.Incidental fixes:
verbose_to_filternever matched theq2bin crate's tracing targets; the tracing fmt layer wrote to stdout instead of stderr.🤖 Generated with Claude Code