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.

#

When presentation should follow an instance's STATE — a status, a severity, a phase — declare a Instance Variant on the class's View rather than styling the affected instances. The variant's When Condition matches a property value, and every instance in that state follows it.

State-driven presentation is the one case that looks like it needs per-instance styling and does not. A variant is still a declaration on the TYPE — one rule covers every instance that enters the state, including ones authored later — so it scales the way the rest of the cascade does, while styling each affected instance by hand does not.

#

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 outward from the resource — 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.

Tokens resolve through five levels, closest first:

LevelDeclared onUse it for
instancethe resource itselfa deliberate one-off
varianta matching Instance Variant on its Viewpresentation that follows state
classany class in its type chainthe normal place to style
publisheryour Publisher resourcethe site-wide palette
floorResourcethe defaults you inherit

Set the palette at the publisher level and refine per class — a class override beats the site palette, and the site palette beats the floor. Two things worth knowing before you theme: the stock floor is a DARK theme, so a light palette must set every colour token or land half-applied; and every rendered stylesheet carries a provenance header naming which level supplied each token, which is the fastest way to answer "why is this colour not what I set?".

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.

Has Recommended Rule#
TextRationale
#

A site-wide palette SHOULD be declared as Tokens on the publisher's Publisher resource, not repeated per class and not by overriding the Resource floor. Override the floor only to change what an UNSTYLED resource inherits everywhere, including resources from packages you import.

The publisher level exists so one declaration themes a site while leaving each class free to refine it. Repeating a palette per class is duplication waiting to drift. Overriding the floor does work — it is where look-tokens puts the defaults — but it is a blunter instrument: it restyles every resource the render closure reaches, so a change intended for your own pages also lands on imported vocabularies rendered alongside them.