+The rule, in one sentence: a slot property is a JSX attribute value, whole, and nothing else. A stand-in that is stringified, used in an expression (`Symbol.toPrimitive`; reason `coerced`), placed as text, or reached inside template quotes renders **nothing on either face** and reports `ATTRIBUTE_SLOT_POSITION` (dev), so a misuse shows on the first render rather than the first refetch. A stand-in passed in another slot's argument — at the top or nested in plain objects and arrays (`{ nested: { x: row.done } }`, `[row.done]`; a `Map`, `Set` or class instance is not walked) — is not data the server has: the arg carries `undefined` at that path on both faces (the record, and the document face's t=0 fill reads the same), with a finding (reason `arg`, `data.path` when nested). A cyclic arg crosses the border as a cycle (three walks used to overflow the stack on it; the border walk `toBorderForm` — every document-face serialization — is copy-on-write with no per-node bookkeeping on ordinary data, measured at or below its previous cost, and falls back to a memoized clone once a cycle is met past depth 16, with replacements reused so a shared generator is seated once). A value reachable at two arg paths crosses as one (one seat for one generator, the identity the input had); a stand-in shared between two paths is reported once, at its first. On the runtime spread path, a stand-in at a `prop:*` key is a finding (reason `prop`; the compiled form drops `prop:*` as SSR always has). Fill output keys are the fill's, save an explicit reserved set (`$`-prefixed, digit-leading, `length`, `slice`, `t`/`h`/`p`, `then`, `constructor`/`toString`/`valueOf`/`toJSON`): a key that shadows an `Array.prototype` method (`filter`, `at`, `map`) binds as data on both faces. The frame client reports the otherwise-silent failures — an element whose markers can never bind because no fill resolves for the prop, or a called occurrence's args record never arrived (reason `orphan`, `data.why` `fill`/`record`, once per occurrence), and a fill that is not a function or returns a DOM node or a non-object where data was read (reason `fill-shape`). Handler positions get one listener per element and event, dispatching to the current fill's handlers; a rebind that releases a key clears the attribute it wrote; an occurrence's end detaches the listeners it attached, so a kept un-keyed element carries one listener (not one per occurrence that bound it, firing twice) and a dropped occurrence's handler never runs through its disposed fill. On the document face `claims` is armed on a render context derived from the page's for the component's subtree, so the page's elements after a server component keep the pre-slot spread walk. Named `ref`/`on*` on a spread element (`<button {...rest} onClick={row.go} />`) bind like every other handler position: `ssrElement` takes the compiled claim map as an optional seventh argument (a thunk keyed by source index, read only under an armed render context) and reads it — and the spread's own handler keys — through `ssrClaim`'s logic (arrays of refs, merged duplicates, handler tuples, the `server-local` finding). A handler position settles in source order, as the client's `spread`/`merge()` do: the last source that _has_ the key — a spread or a named attribute at its position, whatever the value, `undefined` included — owns it, and a nullish owner binds nothing (`<b {...rest} onClick={cond ? row.go : undefined}>` binds nothing on both sides when `cond` is false); refs merge whatever their order; one marker per position (see the compiler changeset). A merged array ref (`ref={[a, b]} ref={c}`) binds every entry on the template path too. The lazy-asset resolution cache is created on the root render context, so a `lazy()` under a derived context (a Loading boundary, a server-owned frame) and one outside it share one cache — one resolver call per module per request. Under the `serverComponents` compiler option a dynamic `class`/`style` on an intrinsic element compiles to `ssrElementAttribute`, so `class={undefined}` writes nothing where template quotes wrote `class=""` (nullish is "not set", as for every other attribute). `@solidjs/web` now ships `skills/server-components/SKILL.md` (the package's `files`) with the rule, the fill idiom and the diagnostic's reasons.
0 commit comments