EnvironmentSpec
What an environment tells the compiler: its name, the attributes and child elements it accepts, its own value types and its validation rules.
@internote/canvas-sdkOn this page
import { EnvironmentSpec } from '@internote/canvas-sdk';name is also the node's identifier: authors write <name>{…}. The render half of an environment is EnvironmentDefinition, which carries a spec as its compile field or assembles one from its inline fields.
A spec kept in a module of its own, with no React import, is what a server registers before compiling a document, so the vocabulary is accepted without loading the surface that draws it.
#Fields
attributescontinue:.Record<string, CanvasAttrSpec>bodycue.write when the environment declares written), or none when there is no vocabulary.PolicySpecderived{{#net.vertices}} reads. The same declaration a vocabulary node makes for its own, carried onto the emitted environment node; the surface publishes them.Record<string, DerivedSpec>derivedUnavailablestring | nullA derived member is declared once per construct, but whether the construct can compute it may depend on where it is drawn — a graph mark's plane. The semantic pass resolves every #id.member the document reads (bindings, {{…}} in prose and attributes) to its node and asks the node's environment; a reason fails the compile, prefixed with the reference as written.
dynamicbody replaces the vocabulary policy wholesale.SwitchSpec[]implicit:value slot supplies, so code:python sets language. An environment that omits it has no : slot. It may never be the universal continue, which is a reference to another environment and not this one's subject.stringnamestringtypesRecord<string, AttrRefiner>The compiler cannot run JavaScript parsers, so a custom-parsed type arrives as a string and is re-read after the compile. The SDK owns the refiners for the types the language has; an environment owns the ones it invented and registers them here.
Two environments may share a type name only by handing in the same function. Two different parsers under one name would make refinement depend on registration order.
validatevoidCalled once per node of this environment, after the generic pass has checked what is true of every environment: id uniqueness, continue: continuity, the two-environment cap, anchor sanity, vocabulary membership, animatability. What is left is the environment's own, such as a mark belonging to its plane or a series naming categories the axis has.
Report through SemanticContext.report; returning without reporting means the node passed.
valueTypestypes table exactly as vocabulary is folded into its nodes.Record<string, TypeSpec>A library's type table is flat and global, so a type an environment's attributes name has to be in it. Types the language has, such as durations, cue anchors, colour and ratio, stay in the base library.
Declaring a type and parsing it are separate: this says what the compiler should accept, types says how to re-read the value afterwards. A custom-parsed type declares { base: 'string' } here and a refiner there.
An attribute whose value may be finished only at render — one driven by a slider's letter, a derived member or the mark's own readings — is an expr position (D68): { base: 'expr', type: … }. The compiler types it whole, -> applications included, and nothing refines it after. A style attribute's is built by styleValueTypes.
vocabularyRecord<string, CanvasNodeSpec>writtencue.write may wrap in this environment — the code environment's lines. cue.write is the language's, not a vocabulary element, so an environment that writes its content in names the chunks here: they are admitted into the cue's body, and the cue into the environment's derived body.string[]Declared in packages/canvas-sdk/src/environment/compile-spec.ts:65.