Add the ejs-listing-port skill and the listing-template docs it points at (bd-hzsi) - #621
Merged
Merged
Conversation
Three integration tests pin what the listing-template documentation asserts: the `$it.*` spelling with an unconditionally-emitted description envelope, and both halves of the raw-HTML split — a markdown anchor is link-rewritten while a raw `<a href>` is not, and a markdown image is resource-collected while a raw `<img>` is not. All three pass against unmodified production code. They exist so the documented idioms cannot silently rot, and so the two silent failure modes stay pinned as deliberate contract — `RawInline` is a no-op leaf in both `LinkRewriteTransform` and `ResourceCollector` — rather than drifting into an accidental "fix". The image test uses inline-record fields deliberately. When the image is an item document's own front-matter `image:`, that page's own render copies the file regardless, which masks the failure completely.
Add docs/guides/projects/listing-templates.qmd as a sibling of the
Listings guide, and keep listings.qmd's "Custom templates" section tight
with pointers into it.
The new page states what the documentation did not. A template's output
is markdown re-parsed into the page, so a raw-HTML anchor is never
link-rewritten and a raw `<img>` is never resource-collected. Both fail
with no diagnostic and no visible difference in the template text, and
the image case hides itself whenever the image is an item document's own
front-matter `image:` — that page's render copies the file regardless.
The page also covers the description placeholder envelope (the markers
belong outside the `$if$`, not inside, or the items that need a preview
are exactly the ones that lose it), what each built-in layout emits so a
custom template can inherit its CSS, and the fact that reading an absent
value warns with Q-12-10.
The worked before/after is quarto-web's own docs/gallery/gallery.ejs —
raw anchors, a raw thumbnail, a `metadataAttrs()` call, and a nested
loop over a custom field. The Q1 → Q2 mapping table moves here from
listings.qmd and gains the rows that fail silently.
listings.qmd gains the syntax and values it was missing: `${var}`,
`$elseif$`, the pipe list, `outputHref`, the four placeholder markers,
`show.<field>` (with the note that `type: custom` has no default field
set, so every `show.*` is false unless `fields:` is declared),
`table-row`, and `metadata-attrs` — the one value that is unsafe to
interpolate directly, since as markdown its quotes are curled into
invalid HTML.
`path` is described precisely: present whenever an item has a link
target, a document or a record with `href:`, and absent only for a
record with neither.
A thin pointer to docs/guides/projects/listing-templates.qmd with the two silent failure modes stated inline rather than merely named — a skill that only names them gets them skipped, which is what the two ports this is generalized from demonstrated. Fires on the symptoms a user actually sees: Q-12-7, Q-12-9, Q-12-10, Q-12-24, a listing rendering with the built-in layout instead of the custom one, a template dumped verbatim into the page, listing links pointing at .qmd files, listing images 404ing. Ends in a required verification step, because a passing render proves nothing here — neither failure mode produces a diagnostic or a text diff. references/worked-examples.md carries three annotated ports: the minimal link-and-envelope shape, a card grid that was reimplementing the `grid` built-in (its JavaScript prologue becomes `template-params:`, and the first question is whether `type: grid` would do), and the whole-card link, where a markdown link's auto-`<p>` restricts the card body to phrasing content. That last constraint is demonstrated rather than asserted: nesting a `<div>` there force-closes the `<p>`, reparents the card outside the anchor as a sibling, and reconstructs the anchor three times via the adoption-agency algorithm, destroying the whole-card link.
Both pages presented syntax translation as the whole job. Q-12-24 even demonstrated the real fix — its "after" example uses a markdown link — without ever saying why, which is the most reliable way to have a reader copy the shape and miss the reason. Each page now names what the syntax mapping does not cover: a template's output is markdown and Quarto resolves paths only after parsing it, so a raw-HTML anchor keeps its .qmd href and a raw `<img>` is never copied into the site, neither with a warning. Q-12-24's mapping table gains the two rows for exactly those patterns. Prose revised through the reader-expectations methodology, which docs/errors/README.md requires for the stub → complete promotion. The finding both pages shared was a missing connective, not misplaced topic or stress positions.
Contributor
✅ 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. |
Member
|
This is great. Relatedly, I recently added |
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.
Brings the
ejs-listing-portskill into the repo, and fills the documentation gap it needs in order to point at q2's own docs rather than an external guide.The skill has been used to complete two Q1 → Q2 listing ports — the Posit Connect documentation and the Positron website. Its prototype body was a pointer to a guide living outside this repo; that guide also predated
fcd76aebd, which added the "Custom templates" section to the Listings guide and superseded much of it. So the skill here points atdocs/guides/projects/, and those docs carry what the external guide carried.The skill
.claude/skills/ejs-listing-port/SKILL.md— the Q1 EJS → Q2 doctemplate contract, the canonical spellings ($it.<key>$inside$for(items)$;type: customandtemplate:; a neutral extension soQ-12-9stops firing), a port procedure, and a verification step.It carries two semantics in the body rather than by reference, because they are what a port gets wrong and neither is visible in the template's text:
Links and images must be markdown, not raw HTML. A template's output is markdown, re-parsed into the page; only then are paths resolved.
LinkRewriteTransformrewrites.qmdtargets to output URLs, and resource collection notes every image so the file is copied. Both walk parsed markdown nodes, andRawBlock/RawInlineare no-op leaves in each, so a path inside a raw-HTML attribute is invisible to both.For images the same split costs two things rather than one: a markdown image is rewritten and copied, a raw
<img>is neither, so the asset never reaches the output tree. Raw HTML stays fine inside link text — the built-ins do exactly this with[`$image-html$`{=html}]($path$).The skill also records that this is settled design — q2 emits HTML from the AST and has no post-Pandoc DOM stage — so the fix is the markdown path, not HTML parsing.
The description placeholder envelope must be emitted unconditionally. Quarto fills a missing item description from the first paragraph of the rendered item page, but only inside the
description-placeholder-begin/-endmarkers, and only if they sit outside the$if$. The built-in templates gate the whole envelope on$if(description)$, so a template copying that shape loses previews for exactly the items that need them.The image rule has one wrinkle the skill calls out: when the image comes from an item document's own front-matter
image:, that page's render copies the file regardless, so a raw-<img>template looks correct. It only fails for inline-record and custom fields — which is where a ported gallery gets its thumbnails.Triggers are the symptoms a user reports:
Q-12-7,Q-12-9,Q-12-10,Q-12-24, a listing rendering with the built-in layout instead of the custom one, a template dumped verbatim into the page,.qmdhrefs, 404ing images. Verification is inspecting the renderedhrefandsrcvalues and confirming the referenced files exist, since neither failure produces a diagnostic or a text diff.references/worked-examples.mdhas three annotated ports: the minimal link-and-description shape; a card grid reimplementing thegridbuilt-in, where the JavaScript prologue becomestemplate-params:and the first question is whethertype: gridwould do; and a whole-card link, where a markdown link's auto-<p>restricts the card body to phrasing content. That last constraint is shown with a parse tree — nesting a<div>force-closes the<p>, moves the card outside the anchor, and reconstructs the anchor three times.The docs
docs/guides/projects/listing-templates.qmd(new) — both rules above, the anatomy of each built-in layout so a custom template can inherit its CSS, the Q1 → Q2 mapping table (moved here fromlistings.qmdand extended with the rows that fail silently), what doctemplates cannot do, and a verification checklist. The worked before/after is quarto-web's owndocs/gallery/gallery.ejs: one{=html}fence around everything, raw anchors, a raw thumbnail, ametadataAttrs()call, and a nested loop over a custom field, which ports to$for(it.tiles)$with the inner$it$shadowing the outer.docs/guides/projects/listings.qmdkeeps a tight "Custom templates" section and gains what its tables were missing:${var},$elseif$, the pipe list,outputHref, the four placeholder markers,show.<field>,table-row,metadata-attrs. Its line about raw HTML going in a```{=html}block "just as it would in a.qmdfile" was true and read as unqualified permission; it now carries the consequence and links topaths.qmd, which states the same rule for page content.Two values needed a caveat.
show.<field>is false for everything undertype: customunless the listing declaresfields:, since custom listings have no default field set. Andmetadata-attrsmust go inside a```{=html}block — interpolated as markdown its quotes are curled intodata-index=“0”.Q-12-9andQ-12-24stopped at syntax; Q-12-24's "after" example used a markdown link without saying why. Both now name what the syntax mapping does not cover, and Q-12-24's table gains rows for the two patterns that fail silently. Both move fromstatus: stubtocomplete.Tests
Three tests in
crates/quarto-core/tests/integration/listing_pipeline.rspin what the skill and the guide assert: the$it.*spelling with an unconditional envelope yielding a derived description, and both halves of the raw-HTML split. They pass against unmodified code — they keep the documented idioms from rotting, and hold the two failure modes as deliberate contract. The image test uses inline-record fields so it exercises the case the front-matter path masks.Notes
metadata-attrsis bound but has no consumer: no built-in template emits it and nothing readsdata-index/data-categories. The comment athelpers.rs:117saying the list.js sort/filter UI is gated on these attributes cannot be true today — a built-in listing withsort-ui: true, filter-ui: trueemits novalueNames, nonew List, nodata-index.bd-nbv80e33owns that gap; the docs describe the value and how to emit it safely, and claim nothing about the filter UI.bd-o1meelim(a leading/intemplate:resolving filesystem-absolute) is a bug whose fix owns the docs sentence about/, so nothing here documents the current behaviour as correct.The epic's suggestion to source the custom-template example from the Quarto extension catalogue turned out not to apply — the catalogue has no listing-template extension, since a listing template is a per-site file rather than something packaged and installed.
Closes bd-hzsi.