# Found some potential bugs in the List API

**URL:** <https://forum.finsweet.com/t/found-some-potential-bugs-in-the-list-api/7479>\
**Category:** Attributes\
**Tags:** attributes-api, list-filter\
**Created:** [August 5, 2026, 6:18pm UTC](https://forum.finsweet.com/t/found-some-potential-bugs-in-the-list-api/7479 "2026-08-05T18:18:49Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![caleb.raney](https://dub1.discourse-cdn.com/flex013/user_avatar/forum.finsweet.com/caleb.raney/32/1349_2.png) [@caleb.raney](https://forum.finsweet.com/u/caleb.raney)\
**Post date:** [August 5, 2026, 6:18pm UTC](https://forum.finsweet.com/t/found-some-potential-bugs-in-the-list-api/7479/1 "2026-08-05T18:18:49Z")

</div>

Hey there, I’m working with claude code to setup a pretty robust events system in Webflow where I can add recurring events into CMS collection lists (based on some CMS fields, recurring events are duplicated and shown in a variety of different layouts - cards, calendar view etc.).

As a part of this I ran into 2 issues with the Attributes API, I was able to find some workarounds but according to claude they were genuine bugs within the api (I had it confirm after looking at the sourcecode of the rep). so I thought I’d run this by your team so you could take a look.  
Apologies if this is totally off-base, I won’t say I understand the issues 100% but thought it would be worth sharing if your team is able to validate and fix them on your end.

Here’s the full bug report I had claude write:

Bug Report: List API — DOM rendering is asynchronous and decoupled from every promise/hook we could find that’s meant to signal “done”

Environment

- `@finsweet/attributes` v2, List module: `<script async type="module" src="https://cdn.jsdelivr.net/npm/@finsweet/attributes@2/attributes.js" fs-list></script>`
- Webflow Collection List with `fs-list-element="list"`, some instances also using `fs-list-load="all"`
- Programmatic API via `window.FinsweetAttributes.push(['list', callback])`, not the declarative filter UI
- Source references below are against the public `finsweet/attributes` GitHub repo, `packages/list/src/`

* * *

## **Bug 1: `createItem()` items hidden on the first pipeline run only**

What we’re doing  
We need to render more cards than there are CMS items — e.g., one CMS entry (“Weekly Class”) should produce 4 separate cards for a given month, one per weekly occurrence. Declarative filtering can’t do this (it filters existing items, it doesn’t multiply them), so we use the JS API. Inside an `addHook('filter', ...)` callback, for each extra occurrence we clone an existing item’s element and register it:

```auto
const clone = originalElement.cloneNode(true);

```

`originalElement.insertAdjacentElement('afterend', clone);`

`const newItem = listInstance.createItem(clone);`

`// newItem is included in the array returned from the filter hook`

This matches the pattern shown in your own docs for `addHook('filter', ...)` and `createItem()`.

The bug  
On page load, the very first time our `filter` hook runs, the cloned elements are correctly inserted into the DOM and correctly included in the array returned from the hook — but they end up with an inline `style="display: none"` applied to them and never become visible.

If the exact same hook runs again immediately after — identical data, identical logic, identical result — the clones render correctly. Every run after the first works correctly, indefinitely. Only the very first pipeline run for a given list instance is affected.

What we ruled out

- Not a data/logic bug on our end — we logged the input/output of our hook on both the broken first run and a working later run; identical in every respect (same computed count, same array length returned).
- Not a load-order issue — deferring our first `triggerHook('filter')` call made no difference.
- Not interceptable via the documented hook lifecycle — we added a cleanup pass on `afterRender` (the last phase in the documented lifecycle: `start → filter → sort → static → pagination → beforeRender → render → afterRender`) that explicitly reset the clones’ `display` style. This did not fix it, meaning whatever applies the hidden style runs after or outside that phase.
- What did work: attaching a `MutationObserver` to each cloned element to watch for `style` changes and immediately revert `display: none`. This confirms something in the List module actively hides elements it doesn’t recognize as part of its own tracked item set, through a code path not reachable via any documented hook.

Related finding  
Your docs show the fully “correct” way to add an item is not just `createItem()` + returning it from a hook, but also:

```auto
const newItem = listInstance.createItem(element);

```

`listInstance.items.value = [...listInstance.items.value, newItem];`

We tried this. Doing it from inside the `filter` hook caused the page to hang/become unresponsive — consistent with an infinite loop, since mutating `items.value` seems to re-trigger the same pipeline the `filter` hook is itself part of. We abandoned this approach as soon as we saw the hang, so we can’t confirm the exact mechanism, but it’s reproducible and severe enough to flag on its own.

* * *

## **Bug 2: `loadingPaginatedItems` resolves before paginated items are actually in the DOM**

What we’re doing  
For a different component, we don’t use `addHook`/`createItem` at all — we only need to know when a `fs-list-load="all"` Collection List has finished loading every Webflow-paginated page, so we can safely scan `wrap.querySelectorAll(...)` ourselves afterward. Per your docs, we await the documented API for this:

```auto
window.FinsweetAttributes.push(['list', (listInstances) => {

```

` const listInstance = listInstances.find((l) => l.listElement === list);`

` Promise.resolve(listInstance.loadingPaginatedItems).then(() => {`

` // scan the DOM for items here`

` });`

`}]);`

The bug  
`loadingPaginatedItems` resolves once every page has been fetched over HTTP and merged into `list.items.value` — confirmed by reading `packages/list/src/load/load.ts`: the promise (`list.loadingPaginatedItems = (async () => { ... })()`, lines 37–49) only awaits `parallelItemsLoad`/`chainedPagesLoad`, both of which fetch pages and push the parsed items into `list.items.value` (`parseLoadedPage`, lines 158–186) — neither function touches the DOM at all.

Actual DOM insertion happens in a completely separate, reactively-triggered pipeline. `List` watches `items` via `@vue/reactivity`’s `watch()` (`components/List.ts` line 576: `watch(this.items, () => this.triggerHook(key), { immediate: true })`), which cascades through the same 8-phase lifecycle as Bug 1 (`start → filter → sort → static → pagination → beforeRender → render → afterRender`). Two things make this pipeline’s completion decoupled in time from `loadingPaginatedItems` resolving:

1. `watch()` from `@vue/reactivity` schedules its callback asynchronously (not synchronously in the same tick as the `items.value` write), so there’s an inherent gap between “data updated” and “render pipeline even starts.”
2. The built-in `render` hook (`components/List.ts` lines 494–564) does `await new Promise(requestAnimationFrame)` **per item** , plus awaits any CSS animations on that item finishing, before considering that item rendered. For a batch of several newly-loaded items, that’s several animation frames (and potentially CSS transition durations) of real wall-clock time between the data being “loaded” and the DOM actually holding those elements.

Concretely reproduced: a 4-page paginated Collection List (`fs-list-load="all"`, page-count element reading “1 / 4”). We awaited `loadingPaginatedItems`, then did a one-time synchronous scan+hide of every item. Result was non-deterministic across reloads — a captured DOM snapshot showed exactly the last page’s 3 items with **no inline style applied at all** (not `display:none` — genuinely untouched by our code), while every earlier page’s items were correctly hidden. They weren’t un-hidden after the fact; they simply weren’t in the DOM yet when our scan ran, despite `loadingPaginatedItems` having already resolved.

What we ruled out

- Not a data bug — same entries/counts logged before and after, only the timing of DOM presence varied.
- Your README documents `loadingPaginatedItems` as “An awaitable Promise that resolves once all the Webflow CMS paginated items have been **loaded**.” That reads as “the items are now present,” but per source it only means “fetched via HTTP and merged into `items.value`.” Nothing next to that property, or anywhere else in the README we found, flags that DOM rendering is a distinct, later, asynchronous step driven by the item lifecycle described elsewhere in the same doc.
- We looked for a more precise “fully rendered” signal to await instead of guessing with a timer. Internal `currentHook`/`queuedHook` state on the `List` instance (`components/List.ts` lines 857–871, cleared at lines 587–588) does track exactly this — it’s `undefined`/`undefined` only once the entire cascade, including `afterRender`, has fully settled with nothing queued behind it. But neither property appears in the README’s “Reactive properties” or “Standard properties” tables, so they’re undocumented internals with no stability guarantee — depending on them felt riskier than the alternative below.

What worked (our workaround): stopped trusting any single “ready” signal from the API and instead watch the DOM directly — debounce on the list container’s `childList` mutations (treat it as “settled” once no new item nodes have been appended for ~150ms) before scanning, plus a standing `MutationObserver` on the container that hides any genuine template item that shows up later than expected, regardless of when it arrives. Implementation-agnostic, but clearly a workaround for a gap that shouldn’t need one.

* * *

## **Questions for your team**

1. Is there a supported way to add items via `createItem()` from within a `filter` hook that doesn’t also require mutating `items.value`?

2. If `items.value` mutation is required, what’s the safe way to do it from inside a hook that belongs to the same pipeline, without triggering re-entrant/infinite execution?

3. Is “elements not in `items.value` get hidden, but only on the first render pass” (Bug 1) expected behavior? If so, is there a hook or signal that would let us handle it reliably, instead of a `MutationObserver` workaround?

4. Is there a supported, documented way to know when the render pipeline triggered by a paginated-items load has fully completed (all `afterRender` passes done, nothing queued) — as opposed to just when the fetch/merge step (`loadingPaginatedItems`) has completed?

5. If not, would you consider clarifying `loadingPaginatedItems`’s doc entry to explicitly state that it guarantees pages have been fetched and merged into `items.value`, but not that they’ve been rendered into the DOM yet?

6. Are `currentHook`/`queuedHook` intended to be a stable, public signal for “pipeline fully idle”? If so, could they be documented and officially exposed as a supported way to detect full settle, rather than something we only found by reading source?

Environment

- `@finsweet/attributes` v2, List module: `<script async type="module" src="https://cdn.jsdelivr.net/npm/@finsweet/attributes@2/attributes.js" fs-list></script>`
- Webflow Collection List with `fs-list-element="list"`, some instances also using `fs-list-load="all"`
- Programmatic API via `window.FinsweetAttributes.push(['list', callback])`, not the declarative filter UI
- Source references below are against the public `finsweet/attributes` GitHub repo, `packages/list/src/`

* * *

## **Bug 1: `createItem()` items hidden on the first pipeline run only**

What we’re doing  
We need to render more cards than there are CMS items — e.g., one CMS entry (“Weekly Class”) should produce 4 separate cards for a given month, one per weekly occurrence. Declarative filtering can’t do this (it filters existing items, it doesn’t multiply them), so we use the JS API. Inside an `addHook('filter', ...)` callback, for each extra occurrence we clone an existing item’s element and register it:

```auto
const clone = originalElement.cloneNode(true);

```

`originalElement.insertAdjacentElement('afterend', clone);`

`const newItem = listInstance.createItem(clone);`

`// newItem is included in the array returned from the filter hook`

This matches the pattern shown in your own docs for `addHook('filter', ...)` and `createItem()`.

The bug  
On page load, the very first time our `filter` hook runs, the cloned elements are correctly inserted into the DOM and correctly included in the array returned from the hook — but they end up with an inline `style="display: none"` applied to them and never become visible.

If the exact same hook runs again immediately after — identical data, identical logic, identical result — the clones render correctly. Every run after the first works correctly, indefinitely. Only the very first pipeline run for a given list instance is affected.

What we ruled out

- Not a data/logic bug on our end — we logged the input/output of our hook on both the broken first run and a working later run; identical in every respect (same computed count, same array length returned).
- Not a load-order issue — deferring our first `triggerHook('filter')` call made no difference.
- Not interceptable via the documented hook lifecycle — we added a cleanup pass on `afterRender` (the last phase in the documented lifecycle: `start → filter → sort → static → pagination → beforeRender → render → afterRender`) that explicitly reset the clones’ `display` style. This did not fix it, meaning whatever applies the hidden style runs after or outside that phase.
- What did work: attaching a `MutationObserver` to each cloned element to watch for `style` changes and immediately revert `display: none`. This confirms something in the List module actively hides elements it doesn’t recognize as part of its own tracked item set, through a code path not reachable via any documented hook.

Related finding  
Your docs show the fully “correct” way to add an item is not just `createItem()` + returning it from a hook, but also:

```auto
const newItem = listInstance.createItem(element);

```

`listInstance.items.value = [...listInstance.items.value, newItem];`

We tried this. Doing it from inside the `filter` hook caused the page to hang/become unresponsive — consistent with an infinite loop, since mutating `items.value` seems to re-trigger the same pipeline the `filter` hook is itself part of. We abandoned this approach as soon as we saw the hang, so we can’t confirm the exact mechanism, but it’s reproducible and severe enough to flag on its own.

* * *

## **Bug 2: `loadingPaginatedItems` resolves before paginated items are actually in the DOM**

What we’re doing  
For a different component, we don’t use `addHook`/`createItem` at all — we only need to know when a `fs-list-load="all"` Collection List has finished loading every Webflow-paginated page, so we can safely scan `wrap.querySelectorAll(...)` ourselves afterward. Per your docs, we await the documented API for this:

```auto
window.FinsweetAttributes.push(['list', (listInstances) => {

```

` const listInstance = listInstances.find((l) => l.listElement === list);`

` Promise.resolve(listInstance.loadingPaginatedItems).then(() => {`

` // scan the DOM for items here`

` });`

`}]);`

The bug  
`loadingPaginatedItems` resolves once every page has been fetched over HTTP and merged into `list.items.value` — confirmed by reading `packages/list/src/load/load.ts`: the promise (`list.loadingPaginatedItems = (async () => { ... })()`, lines 37–49) only awaits `parallelItemsLoad`/`chainedPagesLoad`, both of which fetch pages and push the parsed items into `list.items.value` (`parseLoadedPage`, lines 158–186) — neither function touches the DOM at all.

Actual DOM insertion happens in a completely separate, reactively-triggered pipeline. `List` watches `items` via `@vue/reactivity`’s `watch()` (`components/List.ts` line 576: `watch(this.items, () => this.triggerHook(key), { immediate: true })`), which cascades through the same 8-phase lifecycle as Bug 1 (`start → filter → sort → static → pagination → beforeRender → render → afterRender`). Two things make this pipeline’s completion decoupled in time from `loadingPaginatedItems` resolving:

1. `watch()` from `@vue/reactivity` schedules its callback asynchronously (not synchronously in the same tick as the `items.value` write), so there’s an inherent gap between “data updated” and “render pipeline even starts.”
2. The built-in `render` hook (`components/List.ts` lines 494–564) does `await new Promise(requestAnimationFrame)` **per item** , plus awaits any CSS animations on that item finishing, before considering that item rendered. For a batch of several newly-loaded items, that’s several animation frames (and potentially CSS transition durations) of real wall-clock time between the data being “loaded” and the DOM actually holding those elements.

Concretely reproduced: a 4-page paginated Collection List (`fs-list-load="all"`, page-count element reading “1 / 4”). We awaited `loadingPaginatedItems`, then did a one-time synchronous scan+hide of every item. Result was non-deterministic across reloads — a captured DOM snapshot showed exactly the last page’s 3 items with **no inline style applied at all** (not `display:none` — genuinely untouched by our code), while every earlier page’s items were correctly hidden. They weren’t un-hidden after the fact; they simply weren’t in the DOM yet when our scan ran, despite `loadingPaginatedItems` having already resolved.

What we ruled out

- Not a data bug — same entries/counts logged before and after, only the timing of DOM presence varied.
- Your README documents `loadingPaginatedItems` as “An awaitable Promise that resolves once all the Webflow CMS paginated items have been **loaded**.” That reads as “the items are now present,” but per source it only means “fetched via HTTP and merged into `items.value`.” Nothing next to that property, or anywhere else in the README we found, flags that DOM rendering is a distinct, later, asynchronous step driven by the item lifecycle described elsewhere in the same doc.
- We looked for a more precise “fully rendered” signal to await instead of guessing with a timer. Internal `currentHook`/`queuedHook` state on the `List` instance (`components/List.ts` lines 857–871, cleared at lines 587–588) does track exactly this — it’s `undefined`/`undefined` only once the entire cascade, including `afterRender`, has fully settled with nothing queued behind it. But neither property appears in the README’s “Reactive properties” or “Standard properties” tables, so they’re undocumented internals with no stability guarantee — depending on them felt riskier than the alternative below.

What worked (our workaround): stopped trusting any single “ready” signal from the API and instead watch the DOM directly — debounce on the list container’s `childList` mutations (treat it as “settled” once no new item nodes have been appended for ~150ms) before scanning, plus a standing `MutationObserver` on the container that hides any genuine template item that shows up later than expected, regardless of when it arrives. Implementation-agnostic, but clearly a workaround for a gap that shouldn’t need one.

* * *

## **Questions for your team**

1. Is there a supported way to add items via `createItem()` from within a `filter` hook that doesn’t also require mutating `items.value`?
2. If `items.value` mutation is required, what’s the safe way to do it from inside a hook that belongs to the same pipeline, without triggering re-entrant/infinite execution?
3. Is “elements not in `items.value` get hidden, but only on the first render pass” (Bug 1) expected behavior? If so, is there a hook or signal that would let us handle it reliably, instead of a `MutationObserver` workaround?
4. Is there a supported, documented way to know when the render pipeline triggered by a paginated-items load has fully completed (all `afterRender` passes done, nothing queued) — as opposed to just when the fetch/merge step (`loadingPaginatedItems`) has completed?
5. If not, would you consider clarifying `loadingPaginatedItems`’s doc entry to explicitly state that it guarantees pages have been fetched and merged into `items.value`, but not that they’ve been rendered into the DOM yet?
6. Are `currentHook`/`queuedHook` intended to be a stable, public signal for “pipeline fully idle”? If so, could they be documented and officially exposed as a supported way to detect full settle, rather than something we only found by reading source?

---

<div class="post-metadata">

**Author:** ![Support-Finn](https://dub1.discourse-cdn.com/flex013/user_avatar/forum.finsweet.com/support-finn/32/5169_2.png) [@Support-Finn](https://forum.finsweet.com/u/Support-Finn)\
**Post date:** [August 6, 2026, 10:32am UTC](https://forum.finsweet.com/t/found-some-potential-bugs-in-the-list-api/7479/2 "2026-08-06T10:32:38Z")

</div>

Hey @caleb.raney!

The behaviors you’re describing — `createItem()` display:none on first-run, `loadingPaginatedItems` resolving before DOM rendering, and the `items.value` mutation causing hooks re-entry — go beyond what our public documentation covers. The hook lifecycle (`filter` → `sort` → `static` → `pagination` → `beforeRender` → `render` → `afterRender`) and the callback API are documented, but the specific timing guarantees, internal item tracking during first-render, and whether `currentHook`/`queuedHook` are intended to be public signals are things only the dev team can speak to definitively.

We can’t confirm yet whether these are bugs vs. intentional internal behaviors, but your reproductions sound solid and the MutationObserver workarounds are reasonable interim approaches while this gets investigated. 🤔

We’re routing this directly to the team that owns the List API. Your 6 questions are exactly the kind of thing they’ll want to dig into — especially the documentation gap around `loadingPaginatedItems` (Q5) and whether `currentHook`/`queuedHook` could be exposed as stable public signals (Q6).

If you have a minimal reproduction — staging URL, CodeSandbox, or similar — that would strengthen the report even further. What you’ve already provided is detailed enough to work with, but a live environment always helps speed things along.

Dev team review timelines depend on their current sprint, but we’ll update you as we hear back and can relay any follow-up between you and the team. 💪
