Skip to content

snapshotBones paints an <li> solid, so a list of cards loses everything inside each card #112

Description

@erikpr1994

snapshotBones reads li, td, th, p and h1–h6 as atomic bones whatever they hold, so a list of cards paints as one solid rectangle per card and nothing inside it draws. config.leafTags only adds to the set, so a project cannot take a tag off it.

dist/extract.js, the isLeaf line:

const isLeaf = children.length === 0 || isMedia || isFormEl || leafTags.has(tag);

Reproduction

<ul>
  <li>
    <article class="card">
      <h3>Upper A</h3>
      <p>2 sections · 5 exercises · ≈42 min</p>
      <footer><button>Edit</button><button>Start</button></footer>
    </article>
  </li>
</ul>

Expected: a card surface with a heading, a line of copy and two controls. Actual: one bone the size of the card. The <li> is on the leaf list, so the walk stops there and the article, the h3, the p and the two buttons never emit.

Our /routines page is exactly this and its capture is two grey slabs. Our routine builder is worse — its structure rail became a <ul> of sections, so a fresh capture replaces a rail of headings, pills and rows with a single 167px block.

The fix

A tag on the leaf list is a leaf while everything it holds is inline; a leaf tag that wraps a block is a container and is walked into like any other. <p>Hello <strong>world</strong></p> stays one bone, because strong computes to display: inline. <li><div class="card">…</div></li> recurses, because the div does not.

// A leaf tag is one bone only while everything it holds is inline. A tag on
// the list that wraps a block — an `li` around a card, a `td` around a panel —
// is a container, and is walked into like any other.
const holdsOnlyInline = children.every(child => {
    const display = getComputedStyle(child).display;
    return display === 'inline' || display === 'contents';
});
const isLeaf = children.length === 0 || isMedia || isFormEl || (leafTags.has(tag) && holdsOnlyInline);

children is already the visible-child list computed above it, and every runs only for a tag on the leaf list that has children, so the extra getComputedStyle calls are bounded by that case.

The leafTags doc comment in dist/types.d.ts says "always captured as a single atomic bone, regardless of children", which this narrows — worth rewording to say the tag is atomic while what it holds is inline.

We run this as a pnpm patch on boneyard-js@1.9.0 today. Happy to send it as a pull request if you would rather have one.

Version: boneyard-js@1.9.0, captured through the CLI (boneyard-js build) against a Next.js 16 app.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions