12. Views
A view is a curated display that data is mapped into: a selection of records, given a shape. Catalogs, carousels, search results, landing pages and a product's own page are all views. Views read; they never write. Like contracts, they refer only to rules, classes and facets.
12.1 Shapes #
A shape is the display side of a view: named slots, filled by map lines exactly like a send contract's
writes (chapter 7.3), in the direction values flow.
shape: card
slots:
cover -> image
listingTitle -> heading
"{format} · {releaseDate}" -> line
least of unit::price -> price
count of unit::status -> available
- The left side of a slot line is an address, an aggregate (chapter 6.3), a template (chapter 6.4.1) or a literal. Nothing else: a shape never computes.
- A slot whose value is empty is left out of the projection. A shape MAY mark a slot
!to drop the record from the view when that slot is empty (!cover -> image). - One shape serves many views: the same
cardin a catalog, a carousel and search. - Machine shapes (
format: json,json-ldor a package-defined feed such as a UCP catalog) give the same projection to software instead of people: agents and feeds get complete, structured facets without reading pages. A view MAY name several shapes (shape: card, feed); they share one selection, one projection's freshness and one read set. - The portable shape. The core package defines
crubal:portable, the default machine shape: every value as{"key", "label", "type", "value"}, wherevaluemay be empty but the other three are always present, and every further member is advisory. A consumer that knows nothing of CRUBAL reads the four and ignores the rest. - Page shapes describe a whole page over one record. Their slots MAY hold other views, which then run with
the page's record as their context (12.2,
of).
12.2 Views #
view: dvds
label: DVDs
select:
level: product
wears: OpticalDisc
when: format is DVD and count of unit::status over 0
pin: P-4QX7, P-9KD2
hide: P-1AAA
order: releaseDate after, title
page: 48
shape: card
filters: condition, genres, decade
index: genres, decade
show: public
fresh: write
| Part | Holds | Default |
|---|---|---|
select |
which records: a level, the classes they wear (all of them must be worn), and a when condition (chapter 6.3); or list: L-…, a hand-made list |
required |
of |
for a view inside a page: how the page's record narrows the selection, as a condition over context (work is context.work and not id is context.id) |
none |
pin · hide |
curation: records shown first, in this order · records never shown | none |
order |
keys to sort by, in priority order; after means newest or largest first, before the reverse |
ID |
page |
records per page | 24 |
shape |
the shape every record takes; several for several outputs (card, feed) |
required |
filters |
the filters shoppers can pick from (12.3) | none |
index |
the filters whose buckets get their own indexable pages (12.5) | none |
show |
the audience the view is for (12.4) | public |
fresh |
write, or a delay such as 5m (12.6) |
write |
terms |
how each audience may use what it's shown (12.5.1) | the package's terms |
Catalogs are views whose selection is by worn classes, so "which catalog is this in" is a global rule
(chapter 6.6) the engine answers from the views' selections. "Also on other formats" is a view inside a
product page: select: level: product, of: work is context.work and not id is context.id. Related-by-facet
carousels are views selected by a shared facet value. Hand-made lists are records (an ordered set of
links with a name and an owner), and a view shows one with select: list: L-….
How a group is shown (its products as separate pages, or as options on one page) is a page shape's choice: a group page whose slots hold a view of its products, or product pages that each link their siblings.
12.3 Filters #
A filter is a sub-index inside its view: for each bucket, the records of the view's selection that fall into it.
filter: condition
facet: unit::condition
buckets:
"Like new": NM, VG
Good: G
Fair: F, P
filter: decade
facet: releaseDate
buckets:
1980s: 1980..1989
1990s: 1990..1999
filter: edition
facet: edition
dependent: label
- Buckets are the map. Each bucket is a name and the values (or a range,
a..b) it holds. A filter with nobucketshas one bucket per value of the facet, named by its label. - Only real values are bucketed. New and unknown values (chapter 9) never reach a bucket, and neither do held values.
- A record is in a bucket when any of its values for the facet is (for a level-prefixed facet, when any of its lower records' values is).
- Counts. For each bucket, the count is the number of records in the view's selection, narrowed by every other filter the shopper has picked, that fall into the bucket. Picking two buckets of one filter means either.
- Dependent facets.
dependent:names facets that follow the filter's facet (chapter 11.2follows); their buckets are counted inside each of its buckets.
12.4 Audiences and roles #
Audiences are few and fixed, so the number of stored variants stays small:
| Audience | Who |
|---|---|
public |
anyone, including crawlers |
member |
signed-in people |
staff |
the owner's people |
owner |
the owner itself |
| named parties | other owners, by domain (chapter 7.8) |
show rules (chapter 6, then: key shown to member) decide what each audience sees: per record, per facet,
per value. A view's show says which audience its stored projection is built for.
Roles are itemized permissions that sit on top of an audience as a filter. They never change projections, caches or indexes.
role: buyer
audience: staff
may:
see: unit::cost, unit::location
edit: condition, unit::price
run: ebayListing, printLabel
review: me:videoCodec
- The verbs are
see(a value is shown),edit(a form may write it),run(a contract may be started) andreview(vocabulary and migration queue items may be decided). Targets are facets, classes, views and contracts;*means all. - A role narrows its audience; it never exceeds it. A grant naming anything its audience can't see MUST be refused at publish.
- A person MAY hold several roles; their grants add up, each capped by its own audience.
- Default deny above
member. A person in thestaffaudience with no role sees and does only whatmembercan. - Contracts use the same grants:
who: caller may run ebayListing(chapter 7.6). A form shows or locks each field byseeandedit. - Roles are definitions in a package, versioned like classes. Which person holds which role is data: a link facet on the person's record, with a source like any other value, so every grant has a history.
12.5 Pages, search engines and structured data #
- The public projection is canonical. It is the one stored, cached and indexed, and the one crawlers see.
Other audiences are served the public projection plus what their
showrules and roles add, at request time. - Indexable filters are declared. Each bucket of a filter listed in
indexgets its own page with a canonical URL (/dvds/comedy,/dvds/1980s). Every other filter combination, and every page after the first, is served as not-to-be-indexed. Crawlers never enter the combinations. - Titles point to facets. A page shape's title slot is an address, usually a derivation or rollup
(
listingTitle -> title); there are no hardwired title recipes. - Structured data comes from the record's kind (its schema.org type per level, chapter 11.2) and each
facet's
same-as, emitted for the public audience only. - New, unknown and held values never appear in a public projection.
12.5.1 AI is not an audience #
An unauthenticated bot is public, whatever it says it is; an AI that signs in is a caller like any other,
with roles. So AI gets no audience of its own. What differs for AI is how the public projection may be
used, which a package or view declares as terms per audience:
terms:
public: search yes, answer yes, train no
| Use | Means |
|---|---|
search |
index the pages and link to them |
answer |
quote or summarise them in answers, with a link |
train |
use them to train models |
A package MAY name other uses. CRUBAL only states the terms; the host turns them into what crawlers and agents read (robots rules for named bots, response headers, a machine-readable terms file), the same split as authentication (chapter 7.6). Agents that shop or act for someone read a view's machine shape and act through contracts as callers (chapter 7).
12.6 Projections #
A view is stored and indexed, never derived on every read. Its projection is one row per selected record (the shape's slots), plus the filter postings and counts.
- The read set. Every view has the set of keys its
select,of,order, slots, filters andshowrules read, worked out at publish. Rules are pure and addresses explicit, so the set is exact. - Staleness. A write to a key in a view's read set, on a record or on anything it reads through (upper levels, shared parts, rollups from lower levels), marks that record's projection stale. Only stale rows are rebuilt.
fresh: writerebuilds in the same transaction as the write.fresh: 5mlets rebuilds batch, and the projection is never older than that.- A new version of a view, shape or filter rebuilds its projection in full.
12.7 The JSON form #
{
"view": "dvds",
"select": { "level": "product", "wears": ["OpticalDisc"], "when": "format is DVD" },
"pin": ["P-4QX7"],
"order": [ { "key": "releaseDate", "dir": "after" }, { "key": "title", "dir": "before" } ],
"page": "48",
"shape": "card",
"filters": ["condition", "genres", "decade"],
"index": ["genres", "decade"],
"show": "public",
"fresh": "write"
}
Shapes use chapter 7.9's map objects for their slots; filters hold facet, buckets ([{"name", "values"} or {"name", "range"}]) and dependent; roles hold audience and may (verb → targets).
12.8 Well-formed views #
A package MUST be refused at publish when:
- a view names a shape, filter, class or list that doesn't exist, or selects a level its classes' kinds don't have;
- a slot, order key or filter facet names a facet the package doesn't define;
- a filter's bucket values aren't real values of its facet, or two buckets of one filter overlap;
- an
indexfilter isn't among the view'sfilters; - a role grants anything its audience can't see, or names a target that doesn't exist;
- a public view's slots or filters read a facet that
showrules hide frompublic.