Skip to content

Lua API: function Meta / function Pandoc filters + Meta<->ConfigValue design (bd-2llqjsms, bd-a9g50za2)#404

Merged
cscheid merged 4 commits into
mainfrom
feature/bd-2llqjsms-lua-meta-pandoc-filters
Jul 20, 2026
Merged

Lua API: function Meta / function Pandoc filters + Meta<->ConfigValue design (bd-2llqjsms, bd-a9g50za2)#404
cscheid merged 4 commits into
mainfrom
feature/bd-2llqjsms-lua-meta-pandoc-filters

Conversation

@cscheid

@cscheid cscheid commented Jul 20, 2026

Copy link
Copy Markdown
Member

Implements doc-level Lua filter handlers and the Meta↔ConfigValue design, closing bd-2llqjsms and bd-a9g50za2 (parent epic bd-grkrb9nj). Design record + phased log: claude-notes/plans/2026-07-20-lua-meta-pandoc-filters.md.

What's here (4 commits, one per phase group)

  1. Marshaling core + function Meta (c4723a68) — new crates/pampa/src/lua/config_value.rs: ConfigValue ↔ native-Lua marshaling following modern pandoc's conventions (scalars stay scalars, Inlines/Blocks userdata lists, List/plain-table compounds; !path/!glob/!expr become typed userdata with a mutable .value). The return path reconciles against the original tree: untouched metadata keeps source_info/merge_op/entry order byte-for-byte; changed/new nodes get By::filter provenance. New quarto.config.{str,md,path,glob,expr,null} constructors mirror the YAML tag system. function Meta is invoked in pandoc's applyFully order; pandoc.read/write meta switched to the native shape (fixing a legacy tagged-table parity bug).
  2. Constructors + doc value (75adf0b7) — pandoc.Pandoc(blocks, meta), pandoc.Meta, the six Meta* coercion constructors, and a shared Pandoc doc metatable (walk, deep clone, right-biased __concat, structural __eq); pandoc.read docs and constructed docs are interchangeable values.
  3. Pandoc/Doc handlers + full walk parity (fcbd359f) — element filter functions now traverse metadata values (per-pass, meta-first, matching pandoc's field order); apply_full_filter is the single applyFully implementation behind both the filter pass and doc:walk. Interim Q-11-6 warning fully retired (code + catalog + tests). New user guide docs/guides/authoring/lua-filters.qmd.
  4. Braid snapshot refresh (8384575f).

Deliberate divergences (registered in crates/pampa/tests/lua-conformance/divergences.md)

  • D-num: metadata numbers are first-class, never stringified (pandoc renders them to MetaString).
  • D-null: explicit YAML null reads as nil; reconciliation keeps null-valued keys across passthrough filters; quarto.config.null() writes one.
  • D-order: pairs(meta) order unspecified (as in pandoc); reconciled new keys append sorted for deterministic output.

Scoreboards & verification

  • Conformance ratchet: 198/203 pass (remaining: doc:normalize() ×2 → follow-up strand bd-62lppjuy; D-num ×1 + SimpleTable ×2 permanent DIVERGENCE).
  • Differential (pandoc 3.9.0.2 oracle): 30/30, including 3 new cases pinning doc-handler order in both traversals and meta-value traversal.
  • pampa 4,250 tests, workspace 10,346 tests, clippy/fmt/custom lints clean; hub-client build:all + test:ci + test:wasm green with rebuilt WASM; new doc_level_handlers_wasm smoke test type-checked against wasm32 (runs in Linux CI).
  • E2E through q2 render verified twice with inspected output (Meta title/subtitle replacement; Pandoc-handler summary block proving order + meta traversal), plus permanent smoke-all fixture filters/meta-handler.qmd.
  • Known-red elsewhere: quarto-hub-mcp live tests time out because sync.automerge.org is currently unreachable — environmental, unrelated to this change.

Behavioral note for reviewers

Element filters now see (and can transform) the merged config metadata, exactly as in pandoc/Q1 — e.g. a Str-uppercasing filter also uppercases the title. The smoke fixture asserts a nonzero meta-walk count rather than pinning merged-config size. This intersects with bd-uf4epv4w (machine-facing metadata exposure) if we later want to scope what user filters can touch.

🤖 Generated with Claude Code

cscheid and others added 4 commits July 20, 2026 15:27
…2llqjsms, bd-a9g50za2)

Phases 1-2 of claude-notes/plans/2026-07-20-lua-meta-pandoc-filters.md.

New module crates/pampa/src/lua/config_value.rs — the Meta<->ConfigValue
design center:
- push_config_value / push_meta: ConfigValue -> native Lua shapes
  (modern-pandoc marshaling: scalars stay scalars, PandocInlines/Blocks
  become Inlines/Blocks userdata lists, Array -> List, Map -> plain
  table with a "Meta"-named metatable at the top level). Path/Glob/Expr
  become LuaConfigSpecial userdata (mutable .value, __tostring);
  explicit null via LuaConfigNull.
- peek_config_value / peek_meta: Lua -> ConfigValue with
  *reconciliation*: subtrees structurally equal to the original keep
  their original nodes (source_info, merge_op, entry order, key_source
  preserved byte-for-byte); changed/new nodes get By::filter
  provenance; edited same-kind containers keep the container node's
  YAML provenance. New map keys append in sorted order (Lua hash
  iteration is nondeterministic).
- quarto.config.{str,md,path,glob,expr,null} constructors mirroring the
  YAML tag system.

function Meta invocation (filter.rs): pandoc applyFully order —
typewise element-walk -> Meta, topdown Meta -> element-walk; nil return
keeps meta; non-table return is a Q-11-4 error. Q-11-6 "unimplemented"
warning narrowed to Pandoc/Doc (still uninvoked; Phase 4).

pandoc.read/write meta switched to the native shape (was the legacy
{t="MetaInlines"} tagged-table encoding — itself a parity bug); the
dead meta_value_to_lua/lua_to_meta_value family removed from types.rs.

Registered divergences (divergences.md): D-num (meta numbers never
stringified), D-null (null reads as nil; reconciliation preserves
null-valued keys across passthrough), D-order (map iteration order
unspecified).

Tests: 41 new config_value unit tests (push/peek/reconcile/constructors),
8 new Meta-handler integration tests (order both traversals, provenance,
Q-11-4), readwrite native-shape pins, Q-11-6 tests updated, smoke-all
E2E fixture filters/meta-handler.qmd (title replacement + new key +
typewise-order marker), manually verified via q2 render (<title> and
subtitle from the handler observed in output).

Verification: cargo build/nextest --workspace green (10330 tests),
clippy/fmt/custom lints green, hub-client build:all + test:ci +
test:wasm green with rebuilt WASM. cargo xtask verify's only failure
was ts-packages/quarto-hub-mcp "live:" tests, which timeout because
wss://sync.automerge.org is currently unreachable (environmental,
unrelated to this change).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hase 3)

New module crates/pampa/src/lua/pandoc_doc.rs:
- pandoc.Pandoc(blocks, meta?) with peekMetaValue-style meta
  normalization via the ConfigValue round-trip; pandoc.Meta; the six
  Meta* coercion constructors (MetaBool strictly typed — mlua's bool
  coercion would have accepted any truthy value, caught red-first;
  MetaString renders numbers as an explicit stringification request;
  MetaList/MetaMap normalize elementwise, numbers stay numeric per
  D-num).
- Shared "Pandoc" doc metatable: walk (element legs + Meta leg in
  applyFully order; Pandoc leg is Phase 4), deep clone, __concat
  (right-biased meta union per pandoc-types Semigroup), __eq
  (source-free structural equality). pandoc.read docs carry the same
  metatable, so read and constructed docs are interchangeable;
  readwrite.rs now delegates its doc conversions here.
- apply_meta_function parameterized by new_source so the filter pass
  attributes to By::filter(path) while doc:walk attributes via the
  Lua stack.

Conformance ratchet (TDD driver — xfails removed first, 12 unexpected
failures observed, then implemented): 196 pass / 7 xfail. Remaining:
normalize x2 (deferred follow-up strand), walk-order x2 (Phase 4
meta-value traversal + Pandoc leg), D-num numbers x1 + SimpleTable x2
(permanent DIVERGENCE).

Also lands the Phase 4 differential spec: 3 new oracle-backed cases
(doc-handlers-typewise-order, doc-handlers-topdown-order,
meta-values-walked-typewise) pinning pandoc 3.9.0.2's applyFully
semantics — meta cleared at case end so the deliberately different
meta wire encodings never reach the comparison; currently xfailed in
differential/xfail.txt until Phase 4 implements the Pandoc handler and
meta-value traversal. Existing oracles regenerated byte-identical.

Verification: workspace nextest 10334 passed; xtask verify Rust legs
(lints/clippy/fmt/build/tests) green, ts leg failed only in the
network-dependent quarto-hub-mcp live tests (sync.automerge.org still
unreachable — environmental); hub-client build:all + test:ci green
with rebuilt WASM.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… Phase 4)

Completes the doc-level filter work of
claude-notes/plans/2026-07-20-lua-meta-pandoc-filters.md.

Element walk parity: pandoc's walkBlocksAndInlines traverses metadata —
walk_meta_config_value (walk.rs) walks MetaInlines/MetaBlocks payloads
with either walker; typewise_pandoc runs meta-then-blocks per pass
(pandoc's field order), topdown_pandoc root-down. Container/scalar
nodes keep provenance; only payload vectors are replaced. Element
filters now see merged config metadata, same as pandoc/Q1 (a Str
uppercaser uppercases the title).

Pandoc/Doc handler: apply_pandoc_function (nil keeps the doc;
doc-shaped return reconciles meta against the pre-handler meta;
Q-11-4 on invalid returns). apply_full_filter is the shared applyFully
implementation used by both the filter pass and doc:walk.

Q-11-6 retired: warning path deleted, catalog entry removed, warning
tests flipped to test_doc_level_handlers_run_without_diagnostics
(invocation effects asserted in the filter_tests suites).

Scoreboards: conformance 198 pass / 5 xfail (normalize x2 ->
bd-62lppjuy follow-up strand; D-num + SimpleTable x2 permanent
DIVERGENCE); differential 30/30 (the 3 Phase-4 spec cases flipped
green). New tests: 7 Pandoc-handler/meta-walk integration tests,
doc_level_handlers_wasm smoke in wasm_lua.rs (type-checked against
wasm32 with -Zbuild-std locally; runs in Linux CI).

Fixture change: smoke-all filters/meta-handler.qmd now asserts a
nonzero Str count (seen-[1-9][0-9]*-strs) instead of an exact count —
the walk traverses merged config metadata, whose size is not the
fixture's to pin. filter_tests' typewise-order test updated 2 -> 3
(title Str now counted).

Docs: new user guide docs/guides/authoring/lua-filters.qmd (element
filters, Meta/Pandoc handlers, native meta representation with
registered divergences, quarto.config.*), linked from guides index;
render verified with q2.

E2E: q2 render with a Str+Meta+Pandoc filter produced
"summary: 7 strs, after meta" in the output HTML (Pandoc last, saw
Meta's value, appended a block; count proves meta traversal).

Verification: pampa 4250 tests green; workspace nextest 10346 green;
clippy + custom lints clean; hub-client build:all + test:ci +
test:wasm green with rebuilt WASM.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@posit-snyk-bot

posit-snyk-bot commented Jul 20, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@cscheid
cscheid merged commit 1a2740b into main Jul 20, 2026
8 checks passed
@cscheid
cscheid deleted the feature/bd-2llqjsms-lua-meta-pandoc-filters branch July 20, 2026 21:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants