CRUBAL

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 card in a catalog, a carousel and search.
  • Machine shapes (format: json, json-ld or 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"}, where value may 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 no buckets has 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.2 follows); 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) and review (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 the staff audience with no role sees and does only what member can.
  • Contracts use the same grants: who: caller may run ebayListing (chapter 7.6). A form shows or locks each field by see and edit.
  • 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 show rules and roles add, at request time.
  • Indexable filters are declared. Each bucket of a filter listed in index gets 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 and show rules 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: write rebuilds in the same transaction as the write. fresh: 5m lets 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:

  1. a view names a shape, filter, class or list that doesn't exist, or selects a level its classes' kinds don't have;
  2. a slot, order key or filter facet names a facet the package doesn't define;
  3. a filter's bucket values aren't real values of its facet, or two buckets of one filter overlap;
  4. an index filter isn't among the view's filters;
  5. a role grants anything its audience can't see, or names a target that doesn't exist;
  6. a public view's slots or filters read a facet that show rules hide from public.

Edit on GitHub · Markdown