Protocol

Look Conventions

The one canonical way to style a Kanonak ontology, and a decision guide for when to use a look, a transformation, or a view.

The look system turns a class into a rendered page with no hand-written HTML or CSS: you attach a look to a type, and every instance of that type renders through it. Whatever a type does not declare, it inherits from its superclass and ultimately from the universal floor on Resource — so every resource has a sane page even before you style anything. This package is the single source of truth for that system; the conventions below are the whole of it.

Start here — what are you trying to produce?

A readable PAGE for a resource (its HTML page + SVG identity)
   -> a LOOK.  Declare derivation.look on the CLASS: a ResourceView of bands.
        - want the resource's icon / glyph?            add look.semanticSvg
        - display name or summary is not the default
          rdfs.label / rdfs.comment?                   add look.displayLabel / displaySummary
        - want to theme its colors?                    add derivation.tokens

A NON-page artifact (JSON, YAML, generated code) -- or a visual the
band catalogue genuinely cannot express (a bespoke chart or matrix)
   -> a TRANSFORMATION.  A tx.Transformation bound via derivation.derivations.

A QUERY that selects, filters, or reshapes instances into a result set
   -> a VIEW.  A view.View, materialized into an EphemeralPackage.

In one line: Look for pages, Derivations + a Transformation for non-page artifacts and novel visuals, and a View for queries. The look is the default for presentation; the other two are for when you are not rendering a resource's own page. Most publishers only ever need looks.

Layer
Guidance Layer
Author
Paul Fryer
Created Date
May 29, 2026

Conventions

#

Choosing an Approach

Three tools present or derive data, and they do not overlap. A look (Look) renders a resource as a page — the default, and almost always what you want. A transformation (Transformation, bound via Derivations) produces a non-page artifact (JSON, YAML, generated code) or a visual the band catalogue cannot express. A view (View) is a query that selects and reshapes instances into a result set. Reach for the look first; only drop to a transformation or a view when you are not rendering a resource's own page.

Has Recommended Rule#
TextRationale
#

To render a resource as a readable page, you SHOULD declare a Look and let the universal renderer produce the HTML, CSS, and SVG. Do NOT hand-author a Transformation that emits page HTML.

Declarative bands express the common page (hero + properties + cross-references) in a fraction of the YAML, with built-in cascade semantics for multi-publisher styling. Hand-rolled HTML transformations diverge across publishers and rot.

#

Author a Transformation (bound via Derivations) only for output that is NOT a resource page — a data format, a generated file, or a bespoke visualization the band catalogue cannot produce.

Transformations are the escape hatch for genuinely novel output. Using one where a look would do throws away the cascade and the shared band library.

#

Use a View when you need to select, filter, or aggregate instances into a NEW result shape — not to style an existing resource. A view materializes into a Ephemeral Package. The full data-View authoring guide is the kanonak.org/view-language protocol; this guide is the visual (look) side of the same view.View base.

A view is a query; a look is presentation. They share the view.View base — a look view is its visual specialization, a data view its projecting one — but the surfaces are disjoint (bands vs projections). Conflating them leads to looks that try to compute and views that try to render; route by intent and follow the matching guide.

#

Style Types, Not Instances

You attach a look to a class (or a package, or a publisher), and every instance of that type renders through it. You almost never style a single resource — you style the concept, and all its instances follow. Styling one instance directly is a rare, deliberate override, not the default move.

Has Required Rule#
TextRationale
#

A Look declaration SHOULD live on a class (or a Package / publisher), so it applies to every instance. Declaring it on an individual instance MUST be a deliberate override, not the way you style a kind of thing.

Styling the type is what makes the system scale: one declaration styles a thousand instances, and the class hierarchy carries it to subtypes. Per-instance looks are duplication waiting to drift.

#

per-instance-styling

Avoid — styling one widget instance. Pull the look up onto the Widget class so every widget renders the same way.

Value
my-publisher.org/widgets/widget-0042: derivation.look: type: look.ResourceView bands: [ { type: look.Hero, look.title: rdfs.label } ]
#

The Resource View

The core pattern: a class declares Look whose value is a Resource View — an ordered list of bands, each rendering one region of the page from the resource's own properties. The band catalogue covers the common page: Hero (title + subtitle + type badges), Property List and Property Table (property values), Reference List / Referenced By (cross-references), Markdown (prose), Embedded Views (nested resources rendered through their own class looks), and more. Bands read existing properties — they do not carry content.

Has Required Rule#
TextRationale
#

A class's page look MUST be a Resource View whose bands is an ordered list of band instances, each pointing (via look.source or a band-specific slot) at a property of the resource. Render order follows list order.

An ordered list of declarative bands reading real properties is what lets the renderer produce a page with no per-class code, and keeps labels and values sourced from the graph (DRY).

#

styled-class

A complete Author page — a hero from rdfs.label / rdfs.comment, the list of works from catalog.wrote, and an incoming-reference grid — every region derived from existing properties.

Value
my-publisher.org/catalog/Author: derivation.look: type: look.ResourceView bands: - { type: look.Hero, look.title: rdfs.label, look.subtitle: rdfs.comment, look.badges: rdfs.type } - { type: look.PropertyList, look.source: catalog.wrote } - { type: look.ReferencedBy }
#

Path-Carrier Bands

The common bands above name a single property in a slot like Source or Title Property. The quantitative and series bands — Stat Row, Distribution, Timeline / Version Diff, Version Delta, Time Plot, a detail-row Reference List, and Diagram channels — need to reach a value that may live one or more steps INTO the instance. As of look@2.0.0 every such *Path slot (metricPath, statPath, mapPath, alphaPath/betaPath, lowerPath/upperPath, hueBy, laneBy, labelPath, badgePath, nodeNote, edgeValue) is a Expression — the SAME vocabulary the SVG tiers and data Views use — NOT a /-separated path string. There are exactly three shapes, chosen by what the step is:

  • A property on the evaluated node → a Property Read whose readSource is a Var Ref of input.
  • A field inside an embedded value → NEST the read: a tx.PropertyRead whose readSource is itself the tx.PropertyRead of the embedding property (e.g. read confidence, then estimateMean off it).
  • A field across a REFERENCE → a Traverse (through the reference property, its step reading off the traversed-to subject); to show a referenced resource's display name, traverse and read Label.

The node bound to input depends on the band: instance-level bands (Distribution, StatRow, VersionDelta) evaluate against the PAGE instance; series and diagram bands evaluate against each sub-resource reached through the band's track / source / entries / relation property. Either way input is the node, and every property is a real reference validated at author time.

Has Required Rule#
TextRationale
#

Every band *Path carrier MUST be a Expression evaluated against the band's node (bound to input) — never a /-separated path string. Use a Property Read for a direct or embedded field (nested for embeds) and a Traverse for a reference step.

A string path matched by stripped local name is a magic string: it fails silently to undefined at render time and is invisible to validation. A typed expression resolves every step through the object model, so kanonak validate (the LookBandPath rule) reports an out-of-scope or misspelled property as an error rather than rendering an empty band.

#

stat-and-distribution

A StatRow cell reading estimateMean out of the embedded confidence estimate — a nested PropertyRead, the embedded-field shape.

Value
# `confidence` is an embedded Estimate on the page instance; # reach its `estimateMean` with a NESTED PropertyRead. Thesis: derivation.look: type: look.ResourceView bands: - type: look.StatRow look.stats: - look.statLabel: "μ" look.statPath: type: tx.PropertyRead tx.readSource: type: tx.PropertyRead tx.readSource: { type: tx.VarRef, tx.varName: input } tx.readProp: confidence tx.readProp: estimateMean
#

badge-via-traverse

A ReferenceList badge that follows each evidence item's strength reference and reads the target's rdfs.label — the reference-step shape (Traverse + a read of the traversed-to subject).

Value
# `strength` is a REFERENCE; show the referenced resource's label. - type: look.ReferenceList look.entries: [supports] look.badgePath: type: tx.Traverse tx.traverseSource: { type: tx.VarRef, tx.varName: input } tx.through: strength tx.step: type: tx.PropertyRead tx.readSource: { type: tx.VarRef, tx.varName: input } tx.readProp: rdfs.label
#

string-path

Invalid as of look@2.0.0 — a /-separated string path. The slot is a tx.Expression; author a nested tx.PropertyRead (read confidence, then betaAlpha) instead. A string here is silently dropped (the slot is an object property) and the band renders empty.

Value
- type: look.Distribution look.alphaPath: "confidence/betaAlpha" # WRONG: string path look.betaPath: "confidence/betaBeta"
#

Visual Identity

A resource's icon is declared with Semantic SVG: one responsive SVG with four visibility tiers — chip, icon, card, full — so the same concept reads as a favicon, a sidebar tile, a grid card, and a hero figure without per-context variants. Each tier is a Expression (typically a Concat over SVG string literals interleaved with Property Read / Display Label), so every substitution resolves through the object model and is validated at author time. The floor on Resource supplies a first-letter glyph, so a class needs this only to override the default.

Has Recommended Rule#
TextRationale
#

A custom Semantic SVG SHOULD author its tiers as Expression values, never as raw string templates, so each embedded property reference is resolved and validated rather than leaking unrendered.

A typed expression fails at kanonak validate time when a referenced property is wrong; a string template fails silently at render time, leaking an unsubstituted placeholder.

#

Display Lenses

Cards, nav links, hero titles, and SVG glyphs all read a resource's display name and summary through two lenses: Display Label and Display Summary. The defaults on Resource are Label and Comment. A class whose name or summary lives in a domain-specific property (say teamName) declares the lens once, and every renderer reads the right value — no need to duplicate the value into rdfs.label.

Has Recommended Rule#
TextRationale
#

When a class's display name or one-line summary is NOT in Label / Comment, it SHOULD declare Display Label / Display Summary pointing at the property that holds it, rather than duplicating the value.

The lens keeps one value in one place and teaches every renderer to find it; duplicating into rdfs.label invites the two copies to drift apart.

#

team-name-lens

The Team class tells every renderer that a team's display name comes from teamName and its summary from teamCharter.

Value
my-publisher.org/org/Team: look.displayLabel: org.teamName look.displaySummary: org.teamCharter
#

The Cascade and the Floor

Looks cascade. A resource's look, semanticSvg, display lenses, and tokens each resolve by walking the type hierarchy — the closest declaration wins — and bottoming out at the universal floor on Resource (published as kanonak.org/universal-look). So you only declare what differs from your superclass, and a class that declares nothing still renders a sensible page. Class-hierarchy overrides merge per facet; a per-instance override replaces.

Has Required Rule#
TextRationale
#

Every resource MUST render even when its class declares no look: the universal floor on Resource provides the default page, glyph, and lenses. A class declares a look only to refine that default.

A guaranteed floor is what makes the system safe to adopt incrementally — you style what matters and inherit the rest, rather than authoring a full page for every class up front.