Query Shapes FULL
The element carrying data-query decides how results render. A template child means a list. A plain subtree means a single record. No mode flags, no configuration.
List Shape
An element whose direct child is a <template> renders the result as a keyed list. This delegates to the same rendering pipeline as data-list, so everything you know about list rendering applies unchanged: row identity via data-key, minimal DOM updates on change, and event delegation.
<tbody data-query="products">
<template>
<tr>
<td data-bind="name"></td>
<td data-bind="price"></td>
</tr>
</template>
</tbody>
The key declared on the query provides row identity, so refreshed data patches rows in place instead of rebuilding them. Declare it once in the query; every element bound to that query inherits it.
Record Shape
An element without a template child treats the single result object as its subtree's binding context. A profile card, a settings panel, a detail pane. A row without a list.
Markup and declaration:
<article data-query="currentUser">
<h2 data-bind="name"></h2>
<p data-bind="title"></p>
<p><span data-bind="unreadCount"></span> unread messages</p>
<p data-bind="lastSeen"></p>
<p data-show="$currentUser.isStale">Refreshing…</p>
</article>
// The example simulates the session service in the page; against a
// real server this is from: '/api/me'.
wildflower.query('currentUser', {
from: () => session.fetchProfile(),
refresh: 'focus',
initial: [{ name: 'Loading…', title: '', unreadCount: '' }]
});
data-bind="name" resolve against the result record. Full $currentUser.* paths keep working for query state, as the stale banner shows. Refresh the profile a few times, or switch tabs and come back: messages keep arriving on the simulated service, and only the changed fields update.
Record Semantics
- A null or missing record is valid. Bound fields render empty and nothing throws. Development builds log a note when a record query resolves to null so you can tell intent from accident.
- Background failures preserve the card. A failed refresh sets
syncErrorand leaves the last good values on screen. - Seed with
initial. The record shown before the first fetch resolves comes from theinitialoption, as in the example above. - Simple paths only. The record context applies to plain dotted paths. Expressions and
$paths pass through untouched, so query state and other entities stay reachable inside the card.
Scalars and the State Surface
Queries register as entities, so the $ accessor exposes their state anywhere in the page with no extra syntax. This is how count badges, loading skeletons, and error banners attach to a query without belonging to any particular shape:
<span data-bind="$products.count"></span> products
<div data-show="$products.isLoading">Loading…</div>
<div data-show="$products.error">
Something went wrong. <button data-action="retry">Retry</button>
</div>
<div data-show="$products.isStale">Refreshing…</div>