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.
| Text | Rationale | |
|---|---|---|
| # | To render a resource as a readable page, you SHOULD declare a | 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 | 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 | A view is a query; a look is presentation. They share the |
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.
| Text | Rationale | |
|---|---|---|
| # | A | 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.
| Text | Rationale | |
|---|---|---|
| # | A class's page look MUST be a | 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
readSourceis aVar Ref of
input. - A field inside an embedded value → NEST the read: a
tx.PropertyReadwhosereadSourceis itself thetx.PropertyReadof the embedding property (e.g. readconfidence, thenestimateMeanoff it). - A field across a REFERENCE → a
Traverse (
throughthe reference property, itsstepreading off the traversed-to subject); to show a referenced resource's display name, traverse and readLabel.
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.
| Text | Rationale | |
|---|---|---|
| # | Every band | 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 |
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.
| Text | Rationale | |
|---|---|---|
| # | A custom | A typed expression fails at |
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.
| Text | Rationale | |
|---|---|---|
| # | When a class's display name or one-line summary is NOT in | 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.
| Text | Rationale | |
|---|---|---|
| # | Every resource MUST render even when its class declares no look: the universal floor on | 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. |