From 79f3d808dba2b0827a84aac215d3cce80fd80d47 Mon Sep 17 00:00:00 2001 From: Carlos Scheidegger Date: Thu, 23 Jul 2026 14:39:01 -0500 Subject: [PATCH 1/3] q2 create: native CLI command with artifact scaffolding (bd-oa5kd2yr) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement `q2 create project website ` end-to-end from the native CLI, on an ArtifactProvider seam (Rust port of Q1's ArtifactCreator) so future artifact types slot into the same registry. CLI (crates/quarto/src/commands/create/): - Positional path: `q2 create project [title]`, non-interactive; title defaults to the directory name (choice id for ".") with a warning; Q1 directory semantics (merge into existing dir, hard error on existing _quarto.yml/_quarto.yaml, per-file skip-if-exists); .gitignore ensured with `/.quarto/` (Q1's `**/*.quarto_ipynb` dropped — no Q2 producer exists). - Machine path: `q2 create --json` reads a JSON directive from stdin, emits a single JSON result on stdout, and reports errors/warnings as JsonDiagnostic lines on stderr (the `--json-errors` convention); `q2 create --list [--json]` lists the artifact/choice registry; `--dry-run` / `"dry_run": true` report the file plan without writing. quarto-project-create: - Website scaffold brought to Q1-familiar shape: _quarto.yml with website.title (Q2's website pipeline reads website.title — the old project.title placement left navbar/sidebar untitled), navbar, theme: cosmo, css: styles.css, toc: true; new about.qmd + styles.css. project.resources declares styles.css so it reaches _site/ until bd-b87tmmi4 (css:-referenced files not auto-copied) is fixed. No `brand` marker: Q2 deliberately hard-errors (Q-14-1) on an unconfigured brand theme entry. - Default scaffold gains a starter index.qmd. - Consolidated to one scaffold registry: removed the legacy create_project/CreateProjectOptions/ProjectFile/TemplateFile/ get_templates path (no external consumer); templates.rs is constants-only. Tests (TDD; failures recorded in the plan before implementation): - 26 crate tests incl. serde_yaml round-trips of rendered _quarto.yml. - 27 binary-driven CLI tests (tests/integration/create.rs) covering positional/JSON/list/dry-run paths and error contracts. - projectCreate.wasm.test.ts updated for the new scaffolds (6/6 against the rebuilt WASM module). - Workspace suite 10377/10377; full `cargo xtask verify` passed. Follow-ups filed: bd-hh1erpfx (interactive prompting), bd-j9qz7h25 (extension artifact), bd-r1by4u2a (blog scaffold), bd-1h5r22my (docs page), bd-b87tmmi4 (css copy bug, discovered during e2e). Plan: claude-notes/plans/2026-07-23-q2-create-command.md Co-Authored-By: Claude Fable 5 --- .braid/snapshot.jsonl | 39 +- Cargo.lock | 2 + .../plans/2026-07-23-q2-create-command.md | 527 +++++++++++++++ crates/quarto-project-create/Cargo.toml | 6 + .../templates/default/index.qmd.template | 8 + .../templates/website/_quarto.yml.template | 11 +- .../resources/templates/website/about.qmd | 5 + .../resources/templates/website/styles.css | 1 + crates/quarto-project-create/src/lib.rs | 317 ++++----- crates/quarto-project-create/src/scaffold.rs | 80 ++- crates/quarto-project-create/src/templates.rs | 113 +--- crates/quarto-project-create/src/types.rs | 41 -- crates/quarto/Cargo.toml | 1 + crates/quarto/src/commands/create.rs | 8 - crates/quarto/src/commands/create/artifact.rs | 130 ++++ crates/quarto/src/commands/create/mod.rs | 320 +++++++++ crates/quarto/src/commands/create/project.rs | 217 +++++++ crates/quarto/src/commands/create/writer.rs | 145 +++++ crates/quarto/src/main.rs | 26 +- crates/quarto/tests/integration/create.rs | 609 ++++++++++++++++++ crates/quarto/tests/integration/main.rs | 1 + .../src/services/projectCreate.wasm.test.ts | 33 +- 22 files changed, 2273 insertions(+), 367 deletions(-) create mode 100644 claude-notes/plans/2026-07-23-q2-create-command.md create mode 100644 crates/quarto-project-create/resources/templates/default/index.qmd.template create mode 100644 crates/quarto-project-create/resources/templates/website/about.qmd create mode 100644 crates/quarto-project-create/resources/templates/website/styles.css delete mode 100644 crates/quarto/src/commands/create.rs create mode 100644 crates/quarto/src/commands/create/artifact.rs create mode 100644 crates/quarto/src/commands/create/mod.rs create mode 100644 crates/quarto/src/commands/create/project.rs create mode 100644 crates/quarto/src/commands/create/writer.rs create mode 100644 crates/quarto/tests/integration/create.rs diff --git a/.braid/snapshot.jsonl b/.braid/snapshot.jsonl index 20cc69851..ba2f63f92 100644 --- a/.braid/snapshot.jsonl +++ b/.braid/snapshot.jsonl @@ -31,8 +31,10 @@ {"id":"bd-159sr","title":"Verify @tip-foo crossref of callouts still works after class-vocabulary alignment","description":"The callout class-vocabulary fix (2026-05-22) reshaped\n\\`CalloutResolveTransform\\` output and added a defense-in-depth\nminimal-normalization. \\`CalloutTransform\\` still writes the\ncrossref triple (\\`ref_type\\`, \\`kind\\`, \\`identifier\\`) into\n\\`plain_data\\` for callouts whose id classifies (\\`callout.rs:236-241\\`),\nand the resolver now suppresses the screen-reader span when\n\\`ref_type\\` is present (correct behavior — the crossref-rendered\nprefix announces the type instead).\n\nVerify end-to-end:\n\n1. Render a doc with \\`::: {#tip-foo .callout-tip}\\` and \\`@tip-foo\\`\n elsewhere in the body.\n2. Check the rendered HTML:\n - The callout shows the type's display name (\\\"Tip\\\") in its\n title (default-injected, since no user title).\n - The body's \\`@tip-foo\\` resolves to the crossref-rendered text\n (e.g. \\\"Tip 1\\\" or whatever the locale specifies).\n - The hyperlink target is the callout's outer div id (#tip-foo).\n3. Confirm no screen-reader-only span is emitted on the callout\n (crossref prefix replaces it).\n4. Add a smoke-all fixture under \\`crates/quarto/tests/smoke-all/\\`\n if one doesn't already exist for this pattern.\n\nTracking item carried over from the 2026-05-22 plan's follow-up\nsection. P1 because a regression would silently break a feature\nsome users rely on.","status":"open","priority":1,"issue_type":"task","created_at":"2026-05-26T13:44:11.453179Z","created_by":"gordon","updated_at":"2026-05-26T13:44:11.453179Z"} {"id":"bd-15dw","title":"Navbar icon-only item enrichment tie-break","description":"Phase 3 enrichment fills navbar item.text from profile title only when text is None. An item that supplies icon but no text (common for social links) currently stays text-less. Confirmed intentional. Revisit if users ask for auto-text from titles even for icon-only items. See 2026-04-24-websites-phase-3.md §Follow-up beads.","status":"open","priority":4,"issue_type":"task","created_at":"2026-04-24T19:43:02.249186Z","created_by":"cscheid","updated_at":"2026-04-24T19:43:02.249186Z","dependencies":{"bd-fqyg:discovered-from":{"depends_on_id":"bd-fqyg","type":"discovered-from","created_at":"2026-04-24T19:43:02.249186Z","created_by":"cscheid"}}} {"id":"bd-15uump3h","title":"embed-example-iframe: inline code snippet + Demo crossref for all revealjs examples","description":"Extend the .embed-example-iframe feature so the div can contain a code snippet as its first element, rendered as a code block directly preceding the iframe (second element = caption), matching the Q1 docs appearance (code block -> iframe -> caption). Also give every embed div on docs/presentations/revealjs/index.qmd a #demo-... id so they are @-crossreferenceable, fold the hand-authored code snippets into the divs, and add real captions. Styling to approximate Q1's .slide-deck. Plan: claude-notes/plans/2026-06-10-embed-example-inline-code.md","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-06-10T18:40:09.220583Z","created_by":"Carlos Scheidegger","updated_at":"2026-06-10T23:13:48.396816Z","closed_at":"2026-06-10T23:13:48.396816Z","close_reason":"Implemented + merged to main (49b3d140).","dependencies":{"bd-z1smhvuo:discovered-from":{"depends_on_id":"bd-z1smhvuo","type":"discovered-from","created_at":"2026-06-10T18:40:09.220583Z","created_by":"Carlos Scheidegger"}}} +{"id":"bd-18410csp","title":"Engine-output styling from _quarto-rules: ANSI colors, gt_table, knitsql, widget-subarea, cell-output overflow","description":"Single strand per design discussion (hope: common engine-agnostic stage processes engine output). Port from _quarto-rules.scss: div.ansi-escaped-output + the ~36 .ansi-*-fg/bg color classes (L447–587), table.gt_table rules (L601–647, consumes --quarto-* vars — coordinate with the :root vars strand), .cell-output-display:not(.no-overflow-x)/.knitsql-table overflow (L384), .cell-output-display .widget-subarea margin (L378). Q2 has jupyter + knitr engines; knitr hooks.R already emits cell-output-display. Verify each rule against an EXECUTED fixture (knitr gt table, jupyter ANSI output, widget) before porting — only port what the engines actually emit; leave the rest listed in the strand on close. Inventory row 19.","status":"open","priority":2,"issue_type":"task","created_at":"2026-07-21T17:51:54.894512Z","created_by":"Carlos Scheidegger","updated_at":"2026-07-21T20:19:26.585632Z","labels":["css","parity"],"dependencies":{"bd-4doe9lvt:parent-child":{"depends_on_id":"bd-4doe9lvt","type":"parent-child","created_at":"2026-07-21T17:51:54.894512Z","created_by":"Carlos Scheidegger"}},"comments":{"c-pa0a91vg":{"id":"c-pa0a91vg","author":"Carlos Scheidegger","created_at":"2026-07-21T20:19:26.585632Z","text":"Row 24 (:root --quarto-* CSS vars, _quarto-rules.scss:589-598) moved here from bd-ih6jrf39: the vars (--quarto-body-bg/color/text-muted/border-color/border-width/border-radius) have no consumer in Q2 today; their real consumers are the gt table rules this engine-output strand ports. Port the :root block together with the gt rules so the vars land with a consumer instead of as dead infra."}}} {"id":"bd-18wn","title":"Fix file-locking test failures in quarto-hub on Windows","description":"2 storage tests fail because Windows uses mandatory file locking vs Unix advisory. File::create fails with sharing violation before try_lock_exclusive runs. Fix: use OpenOptions::new().write(true).create(true) instead of File::create, or map sharing violation error to HubAlreadyRunning. Affects test_storage_manager_prevents_double_lock and test_storage_manager_standalone_prevents_double_lock.","status":"open","priority":2,"issue_type":"bug","created_at":"2026-03-20T13:36:06.424470600Z","created_by":"cderv","updated_at":"2026-03-20T13:36:06.424470600Z"} {"id":"bd-195t","title":"Lua attribute-mutation: proxy tables so cb.attr.attributes[k]=v persists","description":"Quarto 2's Lua bridge returns fresh copies of `cb.attr` (and of `cb.attr.attributes`) on every read. In-place mutation like `cb.attr.attributes[\"k\"] = v` — the idiomatic Pandoc-Lua pattern — silently hits an ephemeral copy and is discarded. Surfaced while exercising Phase 3.5's filter-authored spans fixture (04-filter-authored-spans); the workaround there rebuilds the whole Attr with pandoc.Attr(...) and assigns as one value. Before we document the Lua-filter path to syntax highlighting as a user-facing feature, the idiomatic pattern must persist. See claude-notes/plans/2026-04-20-syntax-highlighting-phase-3.5.md 'Follow-up task: Lua attribute-mutation proxy'. Plan: claude-notes/plans/2026-04-21-lua-attr-mutation-proxy.md","status":"closed","priority":1,"issue_type":"bug","created_at":"2026-04-21T17:19:19.121444Z","created_by":"cscheid","updated_at":"2026-07-13T19:20:14.939862Z","closed_at":"2026-07-13T19:20:14.939862Z","close_reason":"Completed via bd-hitjclzp + bd-tzwcof0n: cb.attr.attributes[k]=v (proxies) and cb.attr.classes:insert (classes-as-List + property cache readback) both persist; verified byte-identical to pandoc e2e.","dependencies":{"bd-grkrb9nj:related":{"depends_on_id":"bd-grkrb9nj","type":"related","created_at":"2026-07-13T16:22:47.078681Z","created_by":"Carlos Scheidegger"}},"comments":{"c-6pk8hco9":{"id":"c-6pk8hco9","author":"Carlos Scheidegger","created_at":"2026-07-13T18:19:04.121810Z","text":"Status check 2026-07-13 (during bd-hitjclzp): cb.attr.attributes[k]=v now persists end-to-end (verified vs pandoc oracle) via the existing proxies. Remaining gap: cb.attr.classes:insert('x') is silently lost (classes proxy hands out a detached table for method calls). Overlaps bd-tzwcof0n's classes-as-List work."}}} +{"id":"bd-19nc56ao","title":"ipynb surface syntax: render .ipynb inputs with cell-coordinate source tracking (design + feasibility)","description":"Design and implement .ipynb as a Quarto 2 input format: convert notebook JSON to qmd in-process, with per-cell ephemeral SourceFiles as the coordinate system so diagnostics read 'foo.ipynb[cell 3] line:col' with squiggles over the logical (unescaped) markdown. Supersedes the ipynb half of k-zr88's 2025-12-15 design (sidecar files + NotebookCell SourceInfo variant no longer needed: pipeline is in-memory and pampa's parent_source_info seam already exists). Stored outputs rendered via engine-layer replay (option B). Design doc: claude-notes/plans/2026-07-20-ipynb-surface-syntax-design.md","status":"in_progress","priority":1,"issue_type":"feature","created_at":"2026-07-20T21:50:06.418648Z","created_by":"Carlos Scheidegger","updated_at":"2026-07-20T21:50:49.585143Z","labels":["surface-syntax"],"dependencies":{"bd-kik3s1vt:related":{"depends_on_id":"bd-kik3s1vt","type":"related","created_at":"2026-07-20T21:50:32.216370Z","created_by":"Carlos Scheidegger"},"bd-xxul:related":{"depends_on_id":"bd-xxul","type":"related","created_at":"2026-07-20T21:50:26.424202Z","created_by":"Carlos Scheidegger"},"k-zr88:related":{"depends_on_id":"k-zr88","type":"related","created_at":"2026-07-20T21:50:20.642449Z","created_by":"Carlos Scheidegger"}},"comments":{"c-3wtgfedn":{"id":"c-3wtgfedn","author":"Carlos Scheidegger","created_at":"2026-07-20T21:50:49.585143Z","text":"Design doc written after auditing the 2025-12-15 (k-zr88) and 2025-10-13 (converter registry) designs against the July-2026 codebase. Key findings: run_pipeline is fully in-memory; SourceContext is serde-serializable; pampa read() already accepts parent_source_info making every node SourceInfo a Substring of it (location.rs:214); produce_diagnostic_messages does NOT thread the parent (gap #1); quarto-source-map is affine-only by policy (Transformed variant was removed) — hence the per-cell virtual-file coordinate system design."}}} {"id":"bd-1c6x","title":"Incremental writer panics when original QMD lacks trailing newline","description":"The incremental writer (incremental_write_qmd WASM entry point) panics with 'byte index out of bounds' when the original QMD text doesn't end with a newline. The QMD reader internally pads input with \\n, producing source spans for the padded input, but incremental_write receives the unpadded string. Triggered by changing kanban card status when synced document text doesn't end with \\n. Plan: claude-notes/plans/2026-02-11-incremental-writer-trailing-newline-bug.md","status":"closed","priority":0,"issue_type":"bug","created_at":"2026-02-11T20:48:21.313234Z","created_by":"cscheid","updated_at":"2026-02-11T21:07:59.477860Z","closed_at":"2026-02-11T21:07:59.477843Z","close_reason":"Fixed: added ensure_trailing_newline normalization in incremental_write and compute_incremental_edits. The QMD reader internally pads input with newline, so source spans reference the padded length. The incremental writer now pads its input to match, then strips the padding from results. 6 new tests added, all 6408 workspace tests pass."} {"id":"bd-1d3e","title":"Fix CRLF test failures in quarto-doctemplate on Windows","description":"8 tests fail because template files are checked out as CRLF on Windows but tests compare against LF strings. Options: .gitattributes to force LF on template files, normalize CRLF in compile_from_file at parser.rs:225, or normalize in test assertions. Affects test_partial_resolution and 7 pandoc_equiv_tests.","status":"closed","priority":2,"issue_type":"bug","created_at":"2026-03-20T13:35:56.689880800Z","created_by":"cderv","updated_at":"2026-06-24T15:19:33.752134Z","closed_at":"2026-06-24T15:19:33.752134Z","close_reason":"Abandoned — no commits on branch, CRLF fixes not pursued","external_ref":"https://github.com/quarto-dev/q2/issues/157","comments":{"c-qx8kq21e":{"id":"c-qx8kq21e","author":"cderv","created_at":"2026-06-23T12:39:50.771586Z","text":"Root cause was NOT file-read line endings: normalize_multiline_directives helpers hardcoded '\\n', so CRLF body literals (\\r\\n) failed the multiline-directive detection and doubled blank lines around $if$/$for$/$else$. Reproduces in-memory via Template::compile. Fix preserves input line-ending convention end-to-end (per design decision) by making detection/stripping recognize \\r\\n/\\r/\\n as one unit; fixtures pinned LF via .gitattributes; in-process CRLF regression tests added. PR #329 open against main (commit e2476a8b). doctemplate 200/200; roborev commit+branch reviews both Pass. Full workspace verify deferred to CI."}}} {"id":"bd-1d6io","title":"annotated-qmd: source-tracking off-by-one in regenerated examples","description":"Two TS tests in ts-packages/annotated-qmd fail after regenerating example JSONs with the current pampa writer:\n\n1. substring-invariant.test.ts: 'substring invariant - links.qmd: inline code' — expects substring(125, 133) to be '`x = 5`' but gets ' `x = 5`' (extra leading space).\n2. block-types.test.ts: 'div-attrs.json - Div with attributes conversion' — key source text ' custom-key' has a leading space; the assertion 'should be a valid attribute key' fails because the substring extracted from source includes a preceding space.\n\nBoth fail in the same shape on the surface: the writer-recorded start offset for these tokens is 1 character too early, capturing the preceding whitespace. Whether they share a single root cause has not been confirmed.\n\n## Why this surfaces now (not on main)\n\nThe failures only surface when example fixtures (ts-packages/annotated-qmd/examples/*.json) are regenerated against the current pampa. The committed fixtures (regenerated 2025-10-24, commit 2b2337be by Carlos) predate the pampa changes that introduced the offset drift. cargo nextest run --workspace doesn't touch the fixtures — they're inert JSON loaded by the TS test suite (loadExample(...) in test/document-converter.test.ts and others) — so main's CI has been green throughout while the underlying writer behavior drifted.\n\nPlan 7f Phase 5 (wire-format rename attrS->a, sourceInfoPool->p) requires regenerating the fixtures because the TS code post-rename reads inline.a / astContext.p — old-format fixtures (attrS, sourceInfoPool) would be unreadable. Regeneration is a Phase-5 necessity. The regenerated fixtures capture current pampa writer behavior, including the off-by-one, and that's what trips these tests.\n\nPhase 5 itself only renamed JSON keys — no offset computation was touched. So Phase 5 is the messenger, not the cause.\n\n## Suspected commit for failure #1 (inline code) — NOT CONFIRMED\n\nPlausible candidate: commit 38e889ad (Carlos, 2026-05-24, 'tree-sitter qmd: allow line breaks inside inline code spans and inline math (bd-ilv8p)'). The commit substantially reworked code-span tokenization in the tree-sitter scanner + grammar — SOFT_LINE_ENDING absorb logic, pandoc_soft_break aliasing, an extract_code_span_text helper. It's the only recent commit between 2b2337be (2025-10-24) and HEAD that touches code-span tokenization.\n\nHypothesis: the rework widened the pandoc_code_span tree-sitter node by one byte to the left, to include the preceding-space character. Since Inline::Code's source_info is computed via node_source_info_with_context(node, context) at crates/pampa/src/pandoc/treesitter_utils/code_span_helpers.rs:171 — taking the whole node's byte range — that left-edge widening shows up directly as a 1-char-wider Code source range.\n\nNOT YET BISECTED. The hypothesis is consistent with the failure shape but a clean bisect would confirm or refute it.\n\n## Suspected commit for failure #2 (div key-source) — UNKNOWN\n\nThe div-attrs failure is in a different code path (attribute key-source recording during div parsing). The \"leading space byte gets included\" pattern matches surface-level, but I have NOT verified that this is the same code path or the same root cause. It could be:\n* the same scanner-side absorb pattern leaking into adjacent attr/key-source tokens, OR\n* a separate regression introduced by a different commit, OR\n* older than 2025-10-24 and only now visible because the regenerated fixture happens to record a real source range where the old fixture had something stale.\n\nA clean bisect against the example regen is the cheapest way to find out.\n\n## Repro\n\n cd ts-packages/annotated-qmd\n npm test\n\nFailures: 2 of 156 tests fail. The other 154 pass — including substring-invariant tests on Str / Space / Header / BulletList / ordered-list / link target / link text — which means the off-by-one is not pervasive; it's localized to specific token boundaries.\n\n## Fix direction\n\n1. Bisect each failure independently against the example fixture regen. Use a known-good baseline of pampa from 2025-10-24 (around commit 2b2337be) and walk forward.\n2. If failure #1 lands on 38e889ad, audit pandoc_code_span node-range computation in the scanner — likely the absorb-leading-space behavior needs to start the node at the opening backtick.\n3. If failure #2 lands on a different commit (likely), the fix is independent.\n4. Add a CI lane that regenerates the annotated-qmd example fixtures (or at minimum diffs the writer output against them) so the next time a tree-sitter scanner change drifts the output, this catches at the PR level instead of accumulating until the next forced regen.","status":"in_progress","priority":2,"issue_type":"bug","created_at":"2026-06-01T15:58:10.144959Z","created_by":"gordon","updated_at":"2026-06-01T18:26:49.621585Z","comments":{"c-9upid7i4":{"id":"c-9upid7i4","author":"Gordon Woodhull","created_at":"2026-06-01T18:26:49Z","text":"Investigation complete (worktree bd-1d6io, plan: claude-notes/plans/2026-06-01-bd-1d6io-investigation.md). VERDICT: ready — two distinct scanner-side bugs, both accidental leading-whitespace absorption; Rust writers correct. #1 (inline code): REGRESSION in 2025-10-30/31 inline-parser rewrite (good@2b2337be r:[126,133]; bad@5cc1a849 'code spans'); CODE_SPAN_START token absorbs preceding inline whitespace; 38e889ad REFUTED (parent 5d35218d already bad). #2 (div custom-key): ORIGINAL DEFECT since >=2025-08-06; 2nd+ key_value_key/KEY_SPECIFIER token absorbs inter-pair space; committed [252,262] never matched live pampa even at fixture birth d6230301 (live [251,262]). CI stayed green: insta json snapshots lack inline-Code/multi-kv cases; pandoc-match-corpus is source-range-blind; annotated-qmd JSON is static/hand-regenerated and inert to nextest. FIX: scanner fixes both tokens; add CI-resident byte-offset regression tests (TDD); add xtask verify guard diffing live writer over examples/*.qmd vs committed JSON."}}} @@ -41,6 +43,7 @@ {"id":"bd-1fjtodu8","title":"Lua parity E2: pandoc.List module parity (callable constructor, methods)","description":"List{...} not callable in q2 (attempt to call a table value); align with HsLua List module: callable, :new, insert/remove/sort etc. 7 Track-1 xfails. Catalog: claude-notes/research/2026-07-13-lua-api-mismatch-catalog.md. Epic plan: claude-notes/plans/2026-07-13-lua-api-pandoc-parity.md.","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-07-13T17:09:23.983489Z","created_by":"Carlos Scheidegger","updated_at":"2026-07-13T20:25:24.767246Z","closed_at":"2026-07-13T20:25:24.767246Z","close_reason":"Fixed: List module callable + deep Inlines/Blocks clone; Track-1 98/133, differential 17/17; commit 69f6f435","dependencies":{"bd-grkrb9nj:parent-child":{"depends_on_id":"bd-grkrb9nj","type":"parent-child","created_at":"2026-07-13T17:09:23.983489Z","created_by":"Carlos Scheidegger"}},"comments":{"c-6luxblmj":{"id":"c-6luxblmj","author":"Carlos Scheidegger","created_at":"2026-07-13T20:25:23.523780Z","text":"DONE (2026-07-13 session). pandoc.List module callable via its own metatable __call (hslua-list parity, oracle-probed pandoc 3.9.0.2): List(t)/List{...} set metatable IN PLACE returning same table; List() empty; non-table arg -> 'bad argument #1 to List (table expected, got X)'. Removed stray __call from instance metatable (instances not callable in pandoc — pre-existing q2 divergence). Inlines:clone/Blocks:clone now deep (extract_flushed + fresh userdata per entry); generic List:clone stays shallow (oracle-confirmed). find already returned (value,index); new already in-place — no change needed. SCOREBOARD: Track-1 90->98 pass/35 xfail (flipped: 5 BulletList content, 2 deep-clone, 1 AttributeList); differential 15->17 cases all pass, 0 xfail (new: list-module-callable, list-clone-deep). 6 new integration tests (test_lua_list.rs). Full pampa suite 4094/4094; full cargo xtask verify green; e2e byte-identical to pandoc via real binary. Walk residue unmasked and refiled as bd-2j048yfm (12 xfails incl. C-stack overflow in truncating topdown). Commit 69f6f435 on feature/bd-grkrb9nj-lua-api-pandoc-parity."}}} {"id":"bd-1g5f","title":"Make default sync server configurable via environment variable","description":"Allow build-time configuration of the default Automerge sync server URL via VITE_DEFAULT_SYNC_SERVER environment variable. Currently hardcoded to wss://sync.automerge.org in routing.ts and ProjectSelector.tsx. Needed for internal deployments with private sync servers so beta users don't have to manually enter a custom URL.","status":"closed","priority":2,"issue_type":"feature","created_at":"2026-02-04T21:26:45.230188Z","created_by":"cscheid","updated_at":"2026-02-04T21:48:29.989386Z","closed_at":"2026-02-04T21:48:29.989369Z","close_reason":"Implemented: DEFAULT_SYNC_SERVER now reads from import.meta.env.VITE_DEFAULT_SYNC_SERVER with fallback. Commit 3ed1c1bf."} {"id":"bd-1gty7f7o","title":"LSP directive_tokens: reuse cell_options run detection / language-aware markers","description":"quarto-lsp-core/src/tokens.rs (leading_directive_byte_len, directive_tokens) hardcodes '#|' and re-derives the leading-run detection that quarto_core::cell_options now owns (bd-ohvl879u). quarto-lsp-core already depends on quarto-core. Reusing comment_syntax_for + the run detection makes directive highlighting language-aware (--| lua, //| js, etc.) and keeps one implementation. Keep the highlight-token emission local; only the detection/reassembly should be shared.","status":"open","priority":3,"issue_type":"task","created_at":"2026-07-02T17:20:19.556638Z","created_by":"Carlos Scheidegger","updated_at":"2026-07-02T17:20:19.556638Z","dependencies":{"bd-ohvl879u:discovered-from":{"depends_on_id":"bd-ohvl879u","type":"discovered-from","created_at":"2026-07-02T17:20:19.556638Z","created_by":"Carlos Scheidegger"}}} +{"id":"bd-1h5r22my","title":"docs/ page for q2 create","description":"User-facing docs page for q2 create in docs/ (rendered with Q2 itself): positional usage (project website/default), title defaulting, directory semantics, --dry-run, --list, and the --json machine interface (directive/result shapes, stderr JSON diagnostics) for tool authors. Fold into the docs epic conventions (bd-tr81). Feature shipped in bd-oa5kd2yr; contract documented in claude-notes/plans/2026-07-23-q2-create-command.md (Machine interface section).","status":"open","priority":3,"issue_type":"docs","created_at":"2026-07-23T19:24:00.402713Z","created_by":"Carlos Scheidegger","updated_at":"2026-07-23T19:24:00.402713Z","dependencies":{"bd-oa5kd2yr:discovered-from":{"depends_on_id":"bd-oa5kd2yr","type":"discovered-from","created_at":"2026-07-23T19:24:00.402713Z","created_by":"Carlos Scheidegger"}}} {"id":"bd-1hdz","title":"Title-prefix home-page carve-out (Q1 stem==index parity)","description":"Q1 only injects website.title as pagetitle for the home page (stem == \"index\" && offset === \".\") when both title and pagetitle are absent. Phase 7 takes the broader \"any untitled page falls back to website title\" rule (Decision 4). Revisit if a real fixture surfaces a problem. Originating phase: bd-b9mz.","status":"open","priority":4,"issue_type":"feature","created_at":"2026-04-27T15:03:22.707126Z","created_by":"cscheid","updated_at":"2026-04-27T15:03:22.707126Z","dependencies":{"bd-0tr6:parent-child":{"depends_on_id":"bd-0tr6","type":"parent-child","created_at":"2026-04-27T15:03:22.707126Z","created_by":"cscheid"},"bd-b9mz:discovered-from":{"depends_on_id":"bd-b9mz","type":"discovered-from","created_at":"2026-04-27T15:03:22.707126Z","created_by":"cscheid"}}} {"id":"bd-1hwd","title":"Phase 5: Inline splicing for incremental writer","description":"Implement inline-level splicing in the incremental writer. When a reconciliation plan involves changes that neither add nor remove nodes that create newline characters (SoftBreak, LineBreak), indentation boundaries are preserved and inline changes can be spliced without rewriting the entire indentation boundary. This is a critical optimization for the common case of text edits within paragraphs inside lists and block quotes. Plan: claude-notes/plans/2026-02-10-inline-splicing.md","status":"closed","priority":1,"issue_type":"feature","created_at":"2026-02-10T15:09:50.520536Z","created_by":"cscheid","updated_at":"2026-02-10T16:37:28.833053Z","closed_at":"2026-02-10T16:37:28.833034Z","close_reason":"Implemented inline splicing for incremental writer. All phases complete (5a-5g): safety checks, source span utilities, inline coarsening/assembly, comprehensive property tests, integration. 124 tests across 4 test files. 6394 workspace tests pass.","dependencies":{"bd-2t4o:discovered-from":{"depends_on_id":"bd-2t4o","type":"discovered-from","created_at":"2026-02-10T15:09:50.520536Z","created_by":"cscheid"}}} {"id":"bd-1inj0","title":"Provenance follow-up: code-block decoration synthesizers (Plan 6 §C)","description":"Plan 6's audit identified crates/quarto-core/src/transforms/code_block_generate.rs and code_block_render.rs as a smaller audit pass for code-block decoration synthesizers (filename labels, captions). Deferred from Plan 6 because scope was bounded to the explicitly-enumerated transforms. Likely needs a By::code_block_chrome() (or similar) constructor and a small set of per-transform tests.","status":"open","priority":4,"issue_type":"task","created_at":"2026-05-22T16:19:08.284554Z","created_by":"gordon","updated_at":"2026-05-22T16:19:13.446418Z","dependencies":{"bd-129m3:related":{"depends_on_id":"bd-129m3","type":"related","created_at":"2026-05-22T16:19:13.446359Z","created_by":"gordon"}}} @@ -70,6 +73,7 @@ {"id":"bd-233j","title":"Hub-Client E2E logs are flooded with YAMLWarning [TAG_RESOLVE_FAILED] !str","description":"Playwright e2e runs produce hundreds of warnings of the form:\n\n (node:NNNNN) [TAG_RESOLVE_FAILED] YAMLWarning: Unresolved tag: !str at line 14, column 12:\n - [!str \"
 keep original; userdata -> extract; table -> splice keeping ONLY userdata entries; anything else -> SILENTLY keep original. Result: 'return \"replaced\"' from a Str filter is a silent no-op, and a bare string inside a returned table vanishes. Pandoc coerces returns with the same fuzzy peekers used for constructor args.\n\nTHE FIX. Collapse the userdata/table/other arms into peek_inlines_fuzzy / peek_blocks_fuzzy (crates/pampa/src/lua/types.rs) — they already flush property caches (bd-hitjclzp), so mutation persistence is preserved. MUST NOT change: nil -> keep original; empty table -> delete; the *_with_control second return value (false -> stop traversal).\n\nSUBTLETIES.\n1. Oracle-probe BEFORE implementing (pinned pandoc 3.9.0.2, see tests/lua-conformance/differential/ORACLE_VERSION): does pandoc word-split a multi-word string return from an Inline filter ('return \"two words\"')? And what does a Block filter returning a string produce (expect Plain(word-split))? And what does pandoc do with 'return 5' / 'return true' (expect a raised error)? Use 'pandoc -f markdown x.md -L f.lua -t json' or 'pandoc lua'.\n2. Behavior change: number/boolean returns go from silent no-op to LOUD error (per plan Decision: match pandoc where it accepts; error actionably where it errors). Make the message name the filter function and got-type; Q-coding comes later under bd-9p2686pc.\n3. A3 audit (in scope): shortcode.rs classify_table_result drops non-userdata entries the same way; check Inlines/Blocks list-filter returns (handle_inlines/blocks_return_with_control cover typewise too?) and any doc/meta-level return paths.\n4. In-file tests at the bottom of filter.rs (~line 2340+) pin CURRENT semantics incl. 'integer return keeps original' — update them deliberately to the new contract.\n\nWORKFLOW. TDD via the harnesses: the failing tests exist (differential cases filter-return-string, filter-return-mixed-table). Add differential cases for the oracle probes in (1) (regen with tests/lua-conformance/differential/regen-oracles.sh; CI never needs pandoc). After the fix: LUA_CONFORMANCE_DUMP=1 to regenerate xfail lists (ratchet fails on unexpected passes — remove the two lines from differential/xfail.txt). Then: cargo nextest run -p pampa --features lua-filter (all must pass), cargo xtask verify --skip-hub-build, e2e via the real binary (target/debug/pampa x.md -F f.lua -t html) compared against pandoc, per CLAUDE.md end-to-end policy. Update the plan checklist (item 2.2) + catalog progress log, close this strand with the scoreboard delta, commit on the feature branch. Do NOT import .braid/snapshot.jsonl; braid syncs itself."},"c-ussa2kh0":{"id":"c-ussa2kh0","author":"Carlos Scheidegger","created_at":"2026-07-13T20:07:52.954343Z","text":"DONE (2026-07-13 session). All filter-return paths route through peek_inlines_fuzzy/peek_blocks_fuzzy: six filter.rs handlers + the two ad-hoc typewise list-splice sites (apply_inlines_filter, walk_blocks_straight) which now delegate to new shared handle_inlines_return/handle_blocks_return. Contract oracle-probed (13 probes, pandoc 3.9.0.2): bare string -> word-split (Plain-wrapped in block position); table -> element-wise (string entry -> single Str, no split; number/nested-table entry -> error); Inline userdata from Block filter -> Plain-wrapped; number/boolean -> loud error naming filter fn + got-type (pandoc errors too; Q-coding deferred to bd-9p2686pc). Invariants kept: nil keep, empty-table delete, false stops topdown. A3: shortcode classify_table_result fixed same way (also un-drops inlines in mixed inline/block tables); dead extract_lua_*_from_table deleted; doc-level filter gap filed bd-a9g50za2. SCOREBOARD: differential 8 cases/2 xfail -> 15 cases/0 xfail (7 new oracle cases); Track-1 unchanged 90/133 (cluster was Track-2-only, as the handoff predicted). Verification: 4088/4088 pampa tests, full cargo xtask verify green (incl. WASM+hub legs), e2e byte-identical to pandoc via real pampa binary; 'return 5' exits 1 with actionable message. Commit 61790195 on feature/bd-grkrb9nj-lua-api-pandoc-parity (not pushed). Plan item 2.2 + catalog progress log updated."}}}
+{"id":"bd-28iqotrt","title":"Misc element CSS parity from _quarto-rules: links, iframe, details, columns, hidden utils, footnote-back, light/dark-content, unresolved-ref","description":"Port from _quarto-rules.scss: a { text-underline-offset: 3px } (L436); iframe { margin-bottom: 1em } (L262); details/details[show]/details>summary rules (L267-282 - live for raw-HTML details today; code-fold TODO in code_block_render.rs:182 will land on them); div.columns/div.column (L653 - Q2 emits 
); .hidden/.visually-hidden utilities (L12-26); .footnote-back { margin-left: 0.2em } (L352 - emitted by transforms/footnotes.rs, unstyled today); .quarto-unresolved-ref (L366) - DECIDED 2026-07-21 (Carlos): ADDITIVE emitter change in crossref_render.rs render_resolved_ref - when !resolved, add 'quarto-unresolved-ref' to the class vec alongside the existing 'quarto-xref' (keep Q2's louder '?id?' text + dangling link), then port the CSS. Downstream extensions style .quarto-unresolved-ref to loudly flag missing refs. Inventory rows 1a/10/11/15a/17/22/25. NOTE: row 29 (light/dark-content) split out to bd-l1rx9yzh. bd-btjkyylx TDD template.","status":"closed","priority":2,"issue_type":"task","created_at":"2026-07-21T17:51:52.316128Z","created_by":"Carlos Scheidegger","updated_at":"2026-07-21T20:29:41.638877Z","closed_at":"2026-07-21T20:29:41.638877Z","close_reason":"Ported 7 misc element rules (1a .hidden/10 iframe/11 details/15a footnote-back/17 unresolved-ref/22 a underline-offset/25 div.columns) into _bootstrap-rules.scss + the row-17 ADDITIVE emitter change in crossref_render.rs (unresolved refs now class='quarto-xref quarto-unresolved-ref'). .visually-hidden skipped (bootstrap). Row 29 split to bd-l1rx9yzh. TDD (emitter + CSS) + e2e; phase5 styles.css 90c78796->b06ec21d. Merged 9d532598.","labels":["css","parity"],"dependencies":{"bd-4doe9lvt:parent-child":{"depends_on_id":"bd-4doe9lvt","type":"parent-child","created_at":"2026-07-21T17:51:52.316128Z","created_by":"Carlos Scheidegger"}}} {"id":"bd-2ag1c","title":"Consider excluding bootstrap-js and clipboard-js stages from q2-preview","description":"BootstrapJsStage (added in bd-4eyf) and ClipboardJsStage (added in bd-j1trh) are in the q2-preview pipeline by default because Q2_PREVIEW_STAGE_EXCLUDED only names math-js, render-html-body, apply-template. Worth checking whether that's the intent.\n\nBoth stages are gated #[cfg(not(target_arch = \"wasm32\"))], so the hub-client (WASM) preview never sees them. Native q2-preview does run them.\n\nTheir WASM-exclusion rationale (bootstrap_js.rs:48-55, clipboard_js.rs:45) is that the hub-client iframe reinitializes on every render tick, wiping any state held by Bootstrap components. That argument applies just as well to native q2-preview, which also targets the iframe.\n\nBoth stages mutate only ctx.artifacts; neither touches doc.ast.meta or doc.ast.blocks. The downstream consumer that turns js:* artifacts into