# Source format reference

Read and edit the JSON source behind each Studio tool, including the portable CanvasDocument, its validation rules, migrations and safe round trips.

Studio source is the structured JSON state behind the active artifact. People edit it in the source drawer, which the **Code** button opens. Scripts and agents read and apply it through the Browser API with `window.glyphfield.studio.readSource()` and `applySource()`.

The source stays available while its Studio tool is active, whether or not the source drawer is open. Brand book source holds only presentation edits (order, visibility, layout, surface, image and copy). Its content still comes from the Brand identity tool.

## Rules for every tool [#rules-for-every-tool]

* Source must parse as a JSON object. Arrays and primitive top-level values are rejected.
* Each tool validates its own enums, ranges, references and required collections before it changes visible state.
* When application fails, the current artifact stays unchanged and the error is reported.
* A successful application updates the same state that the inspector, preview, persistence and export use.
* Browser source is not a `POST /api/generate` request. Only a `design-sequence` response contains an apply-ready `document`, and that compact version-3 compatibility source is normalized to the current CanvasDocument when it is applied.
* Keep unknown fields. Edit the current document instead of rebuilding an older example, so fields added by newer versions survive.

## Portable CanvasDocument [#portable-canvasdocument]

Design Lab, the layered composition tool, stores its work in the shared CanvasDocument scene graph. The current canvas schema is `schemaVersion: 2`, and the Design Lab metadata records `sourceVersion: 4`.

```json
{
  "schemaVersion": 2,
  "brandId": "gt",
  "capabilities": ["animation", "assets", "constraints", "guides", "history", "layers", "pages", "text"],
  "pageIds": ["<page id>"],
  "pages": {
    "<page id>": {
      "width": 1600,
      "height": 900,
      "background": "#070707",
      "elementIds": ["shader-canvas-1", "text-1", "logo-brand"]
    }
  },
  "elements": {},
  "assets": {},
  "metadata": {
    "tool": "design-lab",
    "designLab": {
      "sourceVersion": 4,
      "ratio": "wide",
      "exportSettings": {},
      "shaderSequence": {},
      "timeline": {},
      "layerShaders": {},
      "groups": [],
      "workspace": { "activeArtboardId": "artboard-main", "activeSurfaceId": "artboard-main", "artboards": [], "canvas": {} }
    }
  }
}
```

The complete document also carries `id`, `title`, timestamps, `revision`, `guides` and the element records. Keep them exactly as `readSource()` returns them.

Each element has bounds, constraints, `hidden`, `locked`, a style with opacity and blend mode, and tool-specific `data` that holds the full layer settings and the `layerType`. A text element keeps its text in `content`. Bounds `x` and `y` are positions. Bounds `width` and `height` are scale factors of the layer's default box. They are not pixel values. Bounds `rotation` is the layer's angle in degrees, clockwise about its center, from −180 to 180. The layer `data` mirrors a nonzero angle as `transform.rotation`, and `bounds.rotation` wins when the two differ. Documents saved before layers could rotate have no `transform.rotation` and load at 0. Effect elements always have rotation 0.

Image and logo elements reference asset records instead of browser-only paths. Identity assets use `brand-asset:<library id>`, and embedded files use `resource:<layer id>`. Layer IDs start with `shader-`, `effect-`, `text-`, `logo-` or `asset-`. Group IDs start with `group-`, and artboard IDs start with `artboard-`. `metadata.designLab.identity` holds the typography snapshot of an imported project.

### Generated compatibility source [#generated-compatibility-source]

`POST /api/generate` with `kind: "design-sequence"` returns an HTTP envelope with `schemaVersion: 1` and a compact `document` whose `version` is `3`. That document uses the fields `composition`, `ratio`, `timeline`, `shaderSequence` and `exportSettings`. It is an input format and is not the current CanvasDocument. Apply it, then call `readSource()` to receive the normalized CanvasDocument shown above.

### Design Lab layer data [#design-lab-layer-data]

| Layer  | Stored state                                                                                                                                                                       |
| ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Shader | Material ID, colors, engine settings, native frame state, captured PNG reference, size, motion, transform, opacity, blend mode                                                     |
| Text   | Text (`content`) with optional `runs`, type role (the role sets the family), weight, size, color, outline, shadow, text effect, shader fill, transform                             |
| Logo   | Brand or custom asset, color, appearance, shader fill, transform                                                                                                                   |
| Image  | Embedded or library asset, crop, focal point, zoom, fit, opacity, transform, appearance, and `stickerFinish` for stickers                                                          |
| Effect | Converter `kind` (Bayer, ASCII, halftone or posterize) and its settings, foreground and background colors and opacity. An image effect stores `effectId` and `effectProps` instead |

`page.elementIds` is the authoritative layer order from back to front. Groups are stored in the Design Lab metadata and must reference valid element IDs. When a shader layer is missing from the stored order, it is inserted behind the content. An authored order is kept.

### Captured shader appearance [#captured-shader-appearance]

A shader application can carry `frameState` and `frameSnapshot`, which have different jobs:

* `frameState` is the editable anchor of the native recipe: provider, floating-point engine time or frame, timeline time, material ID and any captured input that a provider needs. Current records are version 2, and older Paper version-1 anchors still load.
* `frameSnapshot` is `{ version: 1, assetId, width, height }` and references a lossless PNG in `CanvasDocument.assets`. Its ID has the form `shader-frame:<sha256>`, so repeated uses share the same bytes. Keep unknown snapshot fields when editing.

Shader layers keep these records in element `data`. Shader fills on content layers keep them in `metadata.designLab.layerShaders`. Workspace artboards keep their own snapshots. `timeline.timeMs` stores time separately from the older `timeline.frame` export frame index.

An ordinary pause can update `frameState` and the timeline without creating a PNG `frameSnapshot`. Playback, extending the time range, and PNG, JPG, GIF and MP4 export do not add captured-frame assets. Capturing an appearance is a separate, explicit operation.

Captured frames use durable local asset references instead of temporary `blob:` URLs. Portable JSON embeds the PNG bytes, and source application validates and imports them before it changes the composition. A missing frame produces an error that says what is missing, and the layer does not fall back to the default shader. A PNG keeps the captured appearance at its recorded resolution. It does not store an editable Fluid Energy simulation state, and the same procedural settings can render slightly differently on another GPU.

## Tool document map [#tool-document-map]

| Tool                                                                      | Source document                                                                                                                                                                                                                                                                                       | Validation and compatibility                                                                                                                                                                                                                                                                                     |
| ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Brand identity                                                            | Complete `BrandIdentity`                                                                                                                                                                                                                                                                              | Checked against the BrandIdentity JSON Schema from `GET /api/brand/validate`. Keeps the project `id`, `kind`, `revision`, `builtIn` and `presetFingerprint` and unknown fields. Asset and font paths must be `data:` URLs or paths on the Glyphfield origin. Agents merge targeted patches with `identity.apply` |
| Brand elements                                                            | Settings of the selected element (`glyphfield.brand-element` version 2, with `elementId`)                                                                                                                                                                                                             | Selects `elementId` and stores only values that differ from the identity defaults. Version 1 settings apply to the selected element                                                                                                                                                                              |
| Moodboard                                                                 | Boards and tiles (`glyphfield.moodboard` version 2)                                                                                                                                                                                                                                                   | The composition is `showcase`, `campaign`, `system`, `specimen` or `catalog`. Tiles validate variant, span, tone, image and copy. Version 1 still applies                                                                                                                                                        |
| Brand book                                                                | Sections and pages (`glyphfield.brand-book` version 1)                                                                                                                                                                                                                                                | Section order and visibility, page visibility, layout, surface, image and copy (up to 1200 characters per slot). `schema` and `version` are checked. Foundations belong in the identity                                                                                                                          |
| Animation                                                                 | CanvasDocument schema 2 with `metadata.tool: "animation-studio"` and `metadata.animation` (`mode`, `textFrames`, `includeBrandLogo`, `sequenceOrder`, `settings`, `frameSettings`, `sequenceBackground`, `backgroundOverrides`, `playbackRate`, `audio`, `artboards`, `activeArtboardId`, `identity`) | `playbackRate` is greater than 0 and at most 4. Keyframes and text entrances are validated by path. Artboards and audio bytes are embedded                                                                                                                                                                       |
| Lottie                                                                    | CanvasDocument schema 2 with `metadata.studio = { sourceVersion: 1, toolId: "lottie", state }`. `state` holds the appearance, background, canvas preset, edits, playback and the source (raw JSON in `source.data`, or a `.lottie` asset on the `lottie-animation` element)                           | Raw Lottie JSON with a numeric `fr` and a `layers` array can also be applied. It replaces the animation and clears the edits                                                                                                                                                                                     |
| Design Lab                                                                | CanvasDocument schema 2 with Design Lab source version 4                                                                                                                                                                                                                                              | Exact layers, transforms, assets, effects, sequences, saved frames and export settings                                                                                                                                                                                                                           |
| OpenGraph, Terminal card, Partnership lockup, Blog cover, Slide templates | CanvasDocument schema 2 with `metadata.studio = { sourceVersion: 2, toolId, state }`. Pages are artboards (each slide is a page), and elements are layers with a `data.slot` role and brand color roles                                                                                               | Rejects another tool's document or a newer `sourceVersion`. Text layers need `textStyle`, and code layers need a tab. Library assets are pointed back to the identity's files. The mirrored flat fields `title`, `body`, `code` and `language` stay editable. Version-1 flat states migrate                      |
| Color tokens, Typography                                                  | Foundation state bound to the identity, version 2                                                                                                                                                                                                                                                     | Color tokens: the palette, `colorPairings`, `colorSystem` (semantic light and dark roles, scale settings) and per-token opacity. Typography: roles, `typeScale` and specimen settings. Version 1 sources still apply, and font files stay local                                                                  |
| Component library                                                         | Version 2: `pattern`, `family`, `label`, `supportingCopy`, `disabled`, `size`, `density`, `radius`, `borderWidth`, `elevation`, `surface`, `theme`, `codeFormat`, `useBrandDefaults`, `colors`, `typography`, `textScale`, `letterSpacing`, `asset`                                                   | `family` must match the pattern. Version 1 migrates when applied                                                                                                                                                                                                                                                 |

## Safe agent edits [#safe-agent-edits]

```js
const studio = window.glyphfield.studio;
const before = JSON.parse(studio.readSource());

// Keep the envelope and change only the intended element.
const text = Object.values(before.elements)
  .find((element) => element.kind === 'text');
if (!text) throw new Error('No text element exists.');

text.content = 'Every language. One source.';
text.style.opacity = 0.92;

await studio.applySource(before);

const after = JSON.parse(studio.readSource());
if (after.elements[text.id].content !== text.content) {
  throw new Error('The change did not round-trip.');
}
```

Check `describe().source` before reading or applying. Do not write to localStorage, IndexedDB, component state or internal React properties directly.

## Lottie validation [#lottie-validation]

```json
{
  "v": "5.12.2",
  "fr": 30,
  "ip": 0,
  "op": 90,
  "w": 1200,
  "h": 750,
  "layers": []
}
```

The browser player decides which Lottie features render. Recoloring, trimming and frame selection are Studio state. `lottie.export.source` and **Export JSON** write the updated vector document. An imported `.lottie` archive can be replayed and downloaded, but its layers, text and colors cannot be edited.

## Portability checklist [#portability-checklist]

1. Read and keep the current schema and source versions.
2. Keep asset records, page order, unknown metadata and stable element IDs.
3. Apply through the tool validator and read the source again.
4. Save the source or a named design when the work may need to be regenerated.
5. Export the requested rendered format separately.
6. Keep authorized font and asset files with the document when they are not embedded.

[Source code editing](/docs/studio/source-editing), [Studio Browser API](/docs/reference/browser-api) and [Formats and portability](/docs/artifacts/formats-and-portability) cover related tasks. [Version compatibility](/docs/reference/version-compatibility) explains how these independent version numbers relate and how migration works.
