ReviewOS

stacks/stx

$slots.name silently evaluates falsy inside a compound condition

#2098
Closed glennmichael123 opened this 9 days ago · {{ comments.length }} {{ comments.length === 1 ? 'comment' : 'comments' }}
9 days ago

$slots.name works as the whole of a condition. Put it in a compound expression and the condition comes out falsy, whatever the slot's state - so the element silently never renders.

<!-- renders when a toolbar slot is given -->
<th :if="$slots.actions">…</th>

<!-- never renders, even with the slot given and hasRowActions() true -->
<th :if="$slots.actions || hasRowActions()">…</th>

<!-- never renders either -->
<td :if="!$slots.actions && hasRowActions()">…</td>
<template :if="$slots.cell && (!hasSlottedColumns() || column.slotted)">…</template>

Isolated by changing one thing: removing $slots.actions || from the first condition above made the column appear immediately, same page, same data, nothing else touched.

Why it is costly to find

Nothing fails. There is no warning, the compiled output contains the element and all its bindings, and stx typecheck is clean. The element is simply absent from the DOM, so it reads as a data problem or a CSS problem.

It also hides behind short-circuiting. A real case from the Stacks dashboard:

<div :if="searchable() || columnToggle() || exportable() || $slots.toolbar">

That looks fine and behaves fine for every caller that switches on one of the first three. A caller whose only toolbar content comes from the slot gets no toolbar, and nobody notices for as long as no such caller exists.

In our case a whole feature shipped as a no-op: a declarative row-actions column guarded by :if="$slots.actions || hasRowActions()". Compile probes found every guard and binding in the output, stx typecheck passed, three separate control runs on the compiled template looked right - and the actions column was not on the page. Only driving the live page caught it. See stacksjs/stacks#2924.

Expected

$slots.name evaluates to the slot's presence wherever an expression is allowed, the same as any other value in scope - or, failing that, stx refuses the template rather than compiling a condition it cannot evaluate. Quietly resolving to something falsy is the one outcome that cannot be debugged from the output.

Workaround, for anyone hitting this

Keep every $slots reference standalone and split the branches:

<th :if="$slots.actions">…</th>
<th :else-if="hasRowActions()">…</th>

and where a slot needs a fallback, use the slot's own default children rather than a condition:

<slot name="cell" :row="row">
  <!-- rendered when the caller supplies nothing -->
</slot>

Verified on 0.2.605 (packages/stx, fa8a407bfa) and on the 0.2.534 build installed in the Stacks checkout.

Third one of these: #2095 (slot content loses its ) and #2096 (stx typecheck rejects scoped slot props) are the other two, and together they are why a 729-line table component in Stacks had no callers at all.

9 days ago

Fixed on main in 1f16771.

Component instances now carry escaped slot-presence metadata, which their client setup registers as an independent $slots map. Compound OR/AND/negation, bracket access and other reactive expressions can read it without serializing caller slot HTML or inheriting another instance’s presence.

Maintained regressions: packages/stx/test/components/slot-presence-runtime.test.ts and packages/stx/test/native/slot-presence.test.ts, sharing test-utils/slot-presence-fixture.ts. Covers supplied/missing slots, nested web instances, reactive toggles, and minified/unminified native bundles. All three tests (49 assertions) pass; removing the registration makes all three fail. They also passed in CI run 39454919060. Broader local run: 1,312 passed, zero failed.

Version: included in the v0.2.609 source tag (5504a6b). npm latest was still 0.2.607 when checked; published availability is not yet confirmed.

Full CI has a separate rerun pending after correcting an upstream MapPolygon inventory test: https://github.com/stacksjs/stx/actions/runs/39456525701. The previous run also had a router scroll-restoration failure, whose local rerun passed. Overall CI is not yet confirmed green.

Sign in to comment on this issue.