Skip to main content

Template System

┌─────────────────────────────────────────────────────────────────────────────┐
│ TEMPLATE SYSTEM OVERVIEW │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ SCENE │ │ BLOCK │ │ PROJECT │ │
│ │ Template │ │ Template │ │ Template │ │
│ │ 🎬 │ │ 🧩 │ │ 📁 │ │
│ │ Full scene │ │ Reusable │ │ Multi-scene │ │
│ │ with objects │ │ component │ │ complete │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
│ └──────────────────────┴──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────┐ │
│ │ TemplateEnvelopeV1 │ │
│ │ ┌─────────────────────┐ │ │
│ │ │ schemaVersion: 1 │ │ │
│ │ │ meta: {...} │ │ │
│ │ │ payload: {...} │ │ │
│ │ │ requiredAssets: []│ │ │
│ │ └─────────────────────┘ │ │
│ └─────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘

Table of Contents

  1. Overview
  2. Template Types
  3. Schema Structure
  4. Object DTO
  5. Timeline Snippet
  6. Asset References
  7. Template Stores
  8. Apply Pipeline
  9. Save Template Dialog
  10. Template Gallery
  11. Validation Rules
  12. Best Practices
  13. Troubleshooting

Overview

The Template System allows users to save, share, and reuse animation content at different granularity levels. Templates are self-contained packages that include all necessary metadata, objects, timeline data, and asset references.

Key Concepts

┌────────────────────────────────────────────────────────────────┐
│ TEMPLATE WORKFLOW │
├────────────────────────────────────────────────────────────────┤
│ │
│ CREATE SAVE APPLY │
│ ┌───────┐ ┌───────┐ ┌───────┐ │
│ │ Scene │ ──────▶ │ Save │ ────────▶ │ Apply │ │
│ │ or │ export │Dialog │ import │ to │ │
│ │ Block │ │ │ │ Scene │ │
│ └───────┘ └───────┘ └───────┘ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────┐ │ │
│ │ │ Template │ │ │
│ │ │ Store │ │ │
│ │ │ (IndexedDB) │ │ │
│ │ └─────────────┘ │ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ID Remapping + Asset Resolution │ │
│ │ (Prevents collisions, resolves asset URLs) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘
FeatureDescription
Schema VersionCurrently 1 — enables future migrations
Three Template TypesScene, Block, Project
Asset ResolutionBuiltin, Imported, URL-based assets
ID RemappingGenerates new UUIDs on apply to prevent conflicts
Timeline PreservationSaves tracks, keyframes, draw steps, camera

Template Types

Scene Template (🎬)

Complete scene with all objects, background, and timeline.

┌────────────────────────────────────────────────────────────────┐
│ SCENE TEMPLATE │
├────────────────────────────────────────────────────────────────┤
│ │
│ SceneDTO │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ width: 1920 height: 1080 │ │
│ │ durationMs: 10000 │ │
│ │ background: { type: 'solid', color: '#ffffff' } │ │
│ │ objects: [ │ │
│ │ { id: 'obj-1', type: 'text', ... }, │ │
│ │ { id: 'obj-2', type: 'svg', ... }, │ │
│ │ { id: 'obj-3', type: 'image', ... } │ │
│ │ ] │ │
│ │ timeline: { │ │
│ │ tracks: [...], keys: [...], │ │
│ │ drawSteps: [...], camera: [...] │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘

Use Cases:

  • Title cards and intros
  • Lesson scenes
  • Conclusion scenes

Block Template (🧩)

Reusable component group without scene context.

┌────────────────────────────────────────────────────────────────┐
│ BLOCK TEMPLATE │
├────────────────────────────────────────────────────────────────┤
│ │
│ BlockDTO │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ objects: [ │ │
│ │ { id: 'text-1', type: 'text', x: 0, y: 0, ... }, │ │
│ │ { id: 'icon-1', type: 'svg', x: 50, y: 0, ... } │ │
│ │ ] │ │
│ │ anchor: { x: 0.5, y: 0.5 } // Center anchor │ │
│ │ timeline: { │ │
│ │ tracks: [...], keys: [...], │ │
│ │ drawSteps: [...] │ │
│ │ } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ⚡ Blocks insert at cursor position │
│ ⚡ Can include internal animations │
│ │
└────────────────────────────────────────────────────────────────┘

Use Cases:

  • Call-out boxes
  • Icon + label combos
  • Bullet point groups
  • Data tables

Project Template (📁)

Multi-scene template with project-level settings.

┌────────────────────────────────────────────────────────────────┐
│ PROJECT TEMPLATE │
├────────────────────────────────────────────────────────────────┤
│ │
│ ProjectDTO │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ name: "Educational Video Template" │ │
│ │ width: 1920 height: 1080 fps: 30 │ │
│ │ stylePresets: { │ │
│ │ typographyThemeId: 'modern-clean', │ │
│ │ handPresetId: 'pen-black', │ │
│ │ backgroundPresetId: 'whiteboard' │ │
│ │ } │ │
│ │ scenes: [ │ │
│ │ { sceneId: 's1', name: 'Intro', order: 0, ... }, │ │
│ │ { sceneId: 's2', name: 'Content', order: 1, ...}, │ │
│ │ { sceneId: 's3', name: 'Outro', order: 2, ... } │ │
│ │ ] │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘

Use Cases:

  • Complete video templates
  • Course structures
  • Marketing video series

Schema Structure

Template Envelope (Complete Package)

The TemplateEnvelopeV1 is the root container for all templates:

interface TemplateEnvelopeV1 {
schemaVersion: 1; // Always 1 (current version)
meta: TemplateMetaV1; // Searchable metadata
payload: TemplatePayloadV1; // Scene | Block | Project DTO
requiredAssets?: AssetRef[]; // Assets needed by template
}
┌─────────────────────────────────────────────────────────────────┐
│ TemplateEnvelopeV1 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ schemaVersion: 1 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ meta: TemplateMetaV1 │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ id: "template-uuid-123" │ │ │
│ │ │ type: "scene" | "block" | "project" │ │ │
│ │ │ name: "My Template" │ │ │
│ │ │ description: "..." │ │ │
│ │ │ tags: ["education", "intro"] │ │ │
│ │ │ createdAt: "2025-01-01T00:00:00Z" │ │ │
│ │ │ preview: { path?, blobKey?, dataUrl? } │ │ │
│ │ │ aspectRatio: "16:9" │ │ │
│ │ │ durationMs: 10000 │ │ │
│ │ │ useCaseTags: ["education", "coaching"] │ │ │
│ │ │ formatTags: ["title", "intro"] │ │ │
│ │ │ styleTags: ["minimal", "corporate"] │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ payload: SceneDTO | BlockDTO | ProjectDTO │ │
│ │ (Depends on meta.type) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ requiredAssets: AssetRef[] │ │
│ │ [ │ │
│ │ { type: 'builtin', packId: 'tabler', assetId: '...'} │ │
│ │ { type: 'url', url: 'https://...' } │ │
│ │ { type: 'imported', filename: 'logo.png' } │ │
│ │ ] │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘

Template Metadata Fields

FieldTypeRequiredDescription
idstringUnique template identifier (UUID)
typeTemplateType'scene' | 'block' | 'project'
namestringDisplay name
descriptionstringLonger description
tagsstring[]Search tags
createdAtISO 8601Creation timestamp
updatedAtISO 8601Last update timestamp
previewobjectThumbnail reference
aspectRatiostring'16:9', '9:16', '1:1', '4:3'
durationMsnumberTotal duration in milliseconds
authorstringCreator name
packIdstringPack identifier for builtin
useCaseTagsstring[]Use case categories
formatTagsstring[]Format/layout tags
styleTagsstring[]Visual style tags

Tag Categories

┌────────────────────────────────────────────────────────────────┐
│ TAG CATEGORIES │
├────────────────────────────────────────────────────────────────┤
│ │
│ USE CASE TAGS FORMAT TAGS STYLE TAGS │
│ ────────────── ─────────── ────────── │
│ • education • title • minimal │
│ • coaching • intro • corporate │
│ • business • bullet-list • playful │
│ • marketing • steps • bold │
│ • storytelling • comparison • hand-drawn │
│ • social • quote │
│ • callout │
│ • outro │
│ │
│ ASPECT RATIOS │
│ ───────────── │
│ • 16:9 (Landscape - YouTube, presentations) │
│ • 9:16 (Portrait - TikTok, Reels, Stories) │
│ • 1:1 (Square - Instagram posts) │
│ • 4:3 (Classic - Older displays) │
│ │
└────────────────────────────────────────────────────────────────┘

Object DTO

The ObjectDTO interface represents canvas objects in serialized form:

interface ObjectDTO {
id: string; // Object identifier
type: ObjectDTOType; // Object type
x: number; // X position
y: number; // Y position
width: number; // Object width
height: number; // Object height
rotation?: number; // Rotation in degrees
props: Record<string, unknown>; // Type-specific properties

// Animation settings (for simple animations)
animationStart?: number; // Start time in seconds
animationDuration?: number; // Duration in seconds
animationType?: AnimationType; // Animation type
animationEasing?: string; // Easing function
}

Object Types

TypeDescriptionKey Props
textText elementtext, fontSize, fontFamily, color, fontWeight
svgSVG graphicsrc, fill, stroke, strokeWidth
imageRaster imagesrc, opacity, borderRadius
shapeBasic shapeshapeType, fill, stroke, cornerRadius
groupObject groupchildren: ObjectDTO[]
drawingHand drawingpoints, stroke, strokeWidth
videoEmbedEmbedded videovideoUrl, autoplay, muted
svgPathSVG path elementd, stroke, strokeWidth, fill

Animation Types

┌────────────────────────────────────────────────────────────────┐
│ ANIMATION TYPES │
├────────────────────────────────────────────────────────────────┤
│ │
│ fadeIn ░░░░░░░░░░ → ██████████ │
│ Opacity 0 → 1 │
│ │
│ slideIn ← ← ← ▓▓▓▓▓▓▓▓▓▓ │
│ Moves in from off-canvas │
│ │
│ scaleIn · → ○ → ● → ⬤ │
│ Scale 0 → 1 │
│ │
│ drawIn ✏️ ────────▶ │
│ SVG stroke animation │
│ │
│ pathFollow 🔵─────────────────▶ │
│ Object follows SVG path │
│ │
│ typewriter T → Ty → Typ → Type → ... │
│ Character-by-character reveal │
│ │
│ none (No animation) │
│ │
└────────────────────────────────────────────────────────────────┘

Timeline Snippet

Templates preserve full timeline state via TimelineSnippetDTO:

interface TimelineSnippetDTO {
// First-class timeline tracks + keyframes
tracks?: TemplateTimelineTrackDTO[];
keys?: TemplateTimelineKeyDTO[];

// Draw animation
drawSteps?: DrawStepDTO[];

// Camera
cameraMode?: 'static' | 'track' | 'follow-hand' | 'follow-object';
cameraFollowTargetId?: string | null;
camera?: CameraKeyframeDTO[];

// Audio
audio?: AudioClipDTO[];
}

Timeline Track Structure

┌────────────────────────────────────────────────────────────────┐
│ TIMELINE TRACKS │
├────────────────────────────────────────────────────────────────┤
│ │
│ TemplateTimelineTrackDTO │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ id: "track-uuid" (remapped on apply) │ │
│ │ ownerId: "object-uuid" (remapped on apply) │ │
│ │ binding: "style.opacity" (property path) │ │
│ │ type: "numeric" (track data type) │ │
│ │ label: "Opacity" (display name) │ │
│ │ category: "property" (property|camera|draw|audio) │ │
│ │ bounds: { min: 0, max: 1 } (value constraints) │ │
│ │ defaultValue: 1 │ │
│ │ easing: "ease-out" │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ TemplateTimelineKeyDTO │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ id: "key-uuid" (remapped on apply) │ │
│ │ trackId: "track-uuid" (remapped on apply) │ │
│ │ time: 1000 (milliseconds) │ │
│ │ value: 0.5 (numeric value) │ │
│ │ interp: "bezier" (interpolation) │ │
│ │ easing: "ease-in-out" │ │
│ │ tangentMode: "auto" (auto|linear|flat|broken) │ │
│ │ inTangent: { dx: -100, dy: 0 } │ │
│ │ outTangent: { dx: 100, dy: 0 } │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘

Draw Steps & Camera

┌────────────────────────────────────────────────────────────────┐
│ DRAW & CAMERA DATA │
├────────────────────────────────────────────────────────────────┤
│ │
│ DrawStepDTO │
│ ┌────────────────────────────────────────┐ │
│ │ objectId: "obj-uuid" (target object) │ │
│ │ startMs: 0 (start time) │ │
│ │ durationMs: 2000 (draw duration) │ │
│ │ easing: "linear" (optional) │ │
│ └────────────────────────────────────────┘ │
│ │
│ CameraKeyframeDTO │
│ ┌────────────────────────────────────────┐ │
│ │ timeMs: 0 (time in ms) │ │
│ │ x: 960 (pan X) │ │
│ │ y: 540 (pan Y) │ │
│ │ zoom: 1.0 (zoom level) │ │
│ │ easing: "ease-out" (optional) │ │
│ └────────────────────────────────────────┘ │
│ │
│ AudioClipDTO │
│ ┌────────────────────────────────────────┐ │
│ │ assetRef: "audio-uuid" (asset ref) │ │
│ │ startMs: 0 (start time) │ │
│ │ durationMs: 5000 (clip length) │ │
│ │ volume: 0.8 (0-1 range) │ │
│ └────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘

Asset References

Templates track required assets to ensure portability:

type AssetRefType = 'builtin' | 'imported' | 'url';

interface AssetRef {
type: AssetRefType;
assetId?: string; // For builtin: asset ID
packId?: string; // For builtin: pack ID
filename?: string; // For imported: original filename
url?: string; // For url: external URL
mime?: string; // MIME type hint
}
┌────────────────────────────────────────────────────────────────┐
│ ASSET REFERENCE TYPES │
├────────────────────────────────────────────────────────────────┤
│ │
│ BUILTIN ASSETS │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ type: 'builtin' │ │
│ │ packId: 'tabler' (icon pack name) │ │
│ │ assetId: 'arrow-right' (icon identifier) │ │
│ │ │ │
│ │ → Resolves to: /assets/builtin/tabler/arrow-right.svg │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ IMPORTED ASSETS │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ type: 'imported' │ │
│ │ filename: 'company-logo.png' │ │
│ │ mime: 'image/png' │ │
│ │ │ │
│ │ → Resolves to: /api/assets/imported/company-logo.png │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ URL ASSETS │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ type: 'url' │ │
│ │ url: 'https://example.com/image.jpg' │ │
│ │ │ │
│ │ → Used directly (external resource) │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘

[!warning] Asset Resolution When applying templates with imported assets, ensure the original files are available in your project. Missing assets will be replaced with placeholders.


Template Stores

The template system uses a layered store architecture:

┌────────────────────────────────────────────────────────────────┐
│ STORE ARCHITECTURE │
├────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ CompositeTemplateStore │ │
│ │ (Unified interface for all template access) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────┼──────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ USER │ │ BUILTIN │ │ CLOUD │ │
│ │ Store │ │ Store │ │ Store │ │
│ │ (IndexedDB) │ │ (Static) │ │ (Future) │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │
│ PRIORITY ORDER: │
│ 1. User (allows override of builtin) │
│ 2. Builtin (shipped with app) │
│ 3. Cloud (future: team templates) │
│ │
└────────────────────────────────────────────────────────────────┘

Store Interface

All stores implement the TemplateStore interface:

interface TemplateStore {
list(query?: TemplateQuery): Promise<TemplateMetaV1[]>;
get(id: string): Promise<TemplateEnvelopeV1 | null>;
save(template: TemplateEnvelopeV1): Promise<void>;
remove(id: string): Promise<void>;
}

Query Parameters

interface TemplateQuery {
type?: TemplateType; // Filter by template type
tags?: string[]; // Match any tag
useCaseTags?: TemplateUseCaseTag[];
formatTags?: TemplateFormatTag[];
styleTags?: TemplateStyleTag[];
aspectRatio?: TemplateAspectRatio;
search?: string; // Full-text search
packId?: string; // Filter by pack
limit?: number; // Pagination
offset?: number;
}

Apply Pipeline

When applying a template, the system goes through several transformation steps:

┌────────────────────────────────────────────────────────────────┐
│ APPLY PIPELINE │
├────────────────────────────────────────────────────────────────┤
│ │
│ 1. FETCH TEMPLATE │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ templateStore.get(templateId) │ │
│ │ → Retrieves TemplateEnvelopeV1 from store │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 2. ID REMAPPING │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ generateIdMap(objectIds) │ │
│ │ remapObjectIds(object, idMap) │ │
│ │ remapTimelineSnippet(timeline, idMap) │ │
│ │ │ │
│ │ • All object IDs → new UUIDs │ │
│ │ • Track IDs → new UUIDs │ │
│ │ • Keyframe IDs → new UUIDs │ │
│ │ • Cross-references updated (pathFollow, etc.) │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 3. ASSET RESOLUTION │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ resolveAssets(requiredAssets) │ │
│ │ rewriteTemplatePropsWithResolvedAssets(objects) │ │
│ │ │ │
│ │ • Builtin → /assets/builtin/{pack}/{id} │ │
│ │ • Imported → /api/assets/imported/{filename} │ │
│ │ • URL → used directly │ │
│ │ • Missing → fallback placeholder │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 4. CONVERT DTOs TO RUNTIME OBJECTS │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ objectDtoToSceneObject(dto) │ │
│ │ │ │
│ │ • ObjectDTO → SceneObject │ │
│ │ • Apply defaults │ │
│ │ • Validate constraints │ │
│ └────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ 5. APPLY TO SCENE/PROJECT │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Scene template: applySceneTemplate() │ │
│ │ Block template: applyBlockTemplate() │ │
│ │ Project template: applyProjectTemplate() │ │
│ │ │ │
│ │ • Update store state │ │
│ │ • Import timeline data │ │
│ │ • Apply background settings │ │
│ │ • Preload assets │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘

Apply Modes

┌────────────────────────────────────────────────────────────────┐
│ APPLY MODES │
├────────────────────────────────────────────────────────────────┤
│ │
│ SCENE TEMPLATES │
│ ───────────────── │
│ • replace: Clear scene, add template content │
│ • merge: Add template objects to existing scene │
│ │
│ BLOCK TEMPLATES │
│ ──────────────── │
│ • insert: Add at specified position (or cursor) │
│ │
│ PROJECT TEMPLATES │
│ ───────────────── │
│ • Creates entirely new project │
│ • Replaces current project state │
│ │
└────────────────────────────────────────────────────────────────┘

Save Template Dialog

The Save Template Dialog allows users to save current work as templates:

┌────────────────────────────────────────────────────────────────┐
│ SAVE TEMPLATE DIALOG │
├────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ Save as Template [X] │ │
│ ├────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ Template Name: [My Intro Scene_______________] │ │
│ │ │ │
│ │ Template Type: [🎬 Scene ▼] │ │
│ │ │ │
│ │ Description: │ │
│ │ [A clean intro scene with title and... ] │ │
│ │ [subtitle animation_______________________________] │ │
│ │ │ │
│ │ Use Case: │ │
│ │ [✓] education [ ] coaching [✓] business │ │
│ │ [ ] marketing [ ] storytelling [ ] social │ │
│ │ │ │
│ │ Format: │ │
│ │ [✓] title [✓] intro [ ] bullet-list [ ] steps │ │
│ │ │ │
│ │ Preview: [Upload Thumbnail] or [Auto-Generate] │ │
│ │ │ │
│ │ [Cancel] [💾 Save Template] │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────┘

Save Options

OptionDescription
SceneSave entire current scene
BlockSave selected objects as reusable block
ProjectSave entire project with all scenes

[!tip] Saving Blocks To save a block template, first select the objects you want to include, then choose "Block" as the template type.


Browse and apply templates from the Template Gallery:

┌────────────────────────────────────────────────────────────────┐
│ TEMPLATE GALLERY │
├────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ [🔍 Search templates...________________________] │ │
│ │ │ │
│ │ Type: [All ▼] Aspect: [All ▼] Tags: [Select tags...] │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ ┌────────┐ │ │ ┌────────┐ │ │ ┌────────┐ │ │
│ │ │ Preview│ │ │ │ Preview│ │ │ │ Preview│ │ │
│ │ │ 📷 │ │ │ │ 📷 │ │ │ │ 📷 │ │ │
│ │ └────────┘ │ │ └────────┘ │ │ └────────┘ │ │
│ │ Title Card │ │ Intro │ │ Bullet │ │
│ │ 🎬 scene │ │ 🎬 scene │ │ 🧩 block │ │
│ │ [Apply] │ │ [Apply] │ │ [Apply] │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ ┌────────┐ │ │ ┌────────┐ │ │ ┌────────┐ │ │
│ │ │ Preview│ │ │ │ Preview│ │ │ │ Preview│ │ │
│ │ │ 📷 │ │ │ │ 📷 │ │ │ │ 📷 │ │ │
│ │ └────────┘ │ │ └────────┘ │ │ └────────┘ │ │
│ │ Quote Box │ │ Full Proj │ │ CTA Block │ │
│ │ 🧩 block │ │ 📁 project │ │ 🧩 block │ │
│ │ [Apply] │ │ [Apply] │ │ [Apply] │ │
│ └────────────┘ └────────────┘ └────────────┘ │
│ │
│ Source: [All ▼] [📦 Builtin] [👤 User] [☁️ Cloud] │
│ │
└────────────────────────────────────────────────────────────────┘
  • Search: Full-text search across name, description, and tags
  • Type Filter: Scene, Block, or Project templates
  • Aspect Ratio: Filter by canvas dimensions
  • Tag Filters: Use case, format, and style tags
  • Source Filter: Builtin, User, or Cloud templates
  • Preview: Thumbnail preview with metadata
  • Quick Apply: Double-click or click Apply button

Validation Rules

Schema Validation

RuleDescription
schemaVersionMust equal 1 (current version)
meta.idNon-empty string (UUID recommended)
meta.typeOne of: 'scene', 'block', 'project'
meta.nameNon-empty string, max 100 characters
meta.tagsArray of strings
meta.createdAtValid ISO 8601 date string
payloadMust match declared meta.type

Object Validation

RuleDescription
idNon-empty string
typeValid ObjectDTOType
x, yFinite numbers
width, heightPositive finite numbers
rotationNumber (default: 0)
propsObject with type-specific properties

Type Guards

// Check payload type
isSceneDTO(payload); // → true if SceneDTO
isBlockDTO(payload); // → true if BlockDTO
isProjectDTO(payload); // → true if ProjectDTO

// Check envelope type
isSceneTemplate(envelope); // → true if scene template
isBlockTemplate(envelope); // → true if block template
isProjectTemplate(envelope); // → true if project template

Best Practices

Creating Templates

┌────────────────────────────────────────────────────────────────┐
│ TEMPLATE BEST PRACTICES │
├────────────────────────────────────────────────────────────────┤
│ │
│ ✅ DO ❌ DON'T │
│ ───── ──────── │
│ Use builtin assets Include large embedded images │
│ Add descriptive tags Use vague names │
│ Include preview thumbnail Skip metadata │
│ Test template application Use absolute paths │
│ Group related objects Include sensitive data │
│ Use relative positioning Hard-code specific IDs │
│ │
└────────────────────────────────────────────────────────────────┘

Template Organization

PracticeBenefit
Use clear naming conventionsEasy discovery
Add comprehensive tagsBetter search results
Include descriptionsUsers understand purpose
Create preview thumbnailsVisual browsing
Group by use caseLogical organization

[!tip] Block vs Scene Create block templates for reusable components that can be inserted anywhere. Use scene templates for complete layouts with specific timing and background.


Troubleshooting

Common Issues

┌────────────────────────────────────────────────────────────────┐
│ TROUBLESHOOTING │
├────────────────────────────────────────────────────────────────┤
│ │
│ ISSUE: Template not found │
│ ──────────────────────────── │
│ • Check template ID is correct │
│ • Verify template exists in store │
│ • Try refreshing template list │
│ │
│ ISSUE: Missing assets after apply │
│ ──────────────────────────────── │
│ • Imported assets must be re-imported │
│ • Check asset resolution warnings │
│ • Verify builtin pack is available │
│ │
│ ISSUE: Timeline not preserved │
│ ──────────────────────────────── │
│ • Ensure timeline snippet is exported │
│ • Check track/key remapping │
│ • Verify timeline service is available │
│ │
│ ISSUE: Objects positioned incorrectly │
│ ──────────────────────────────────── │
│ • Block templates use anchor for positioning │
│ • Scene templates use absolute positions │
│ • Check canvas dimensions match │
│ │
│ ISSUE: Save fails │
│ ──────────────────────────────── │
│ • Check IndexedDB quota │
│ • Validate required fields │
│ • Check browser storage permissions │
│ │
└────────────────────────────────────────────────────────────────┘

Debug Information

// Check template store status
const templates = await templateStore.list();
console.log('Available templates:', templates.length);

// Validate template before save
console.log('Schema version:', envelope.schemaVersion);
console.log('Template type:', envelope.meta.type);
console.log('Object count:', envelope.payload.objects?.length);
console.log('Required assets:', envelope.requiredAssets?.length);

Summary

The Template System provides a comprehensive solution for saving, sharing, and reusing animation content:

┌────────────────────────────────────────────────────────────────┐
│ SUMMARY │
├────────────────────────────────────────────────────────────────┤
│ │
│ 📦 SCHEMA VERSION 1 │
│ └─ Stable format with migration support │
│ │
│ 🎬 THREE TEMPLATE TYPES │
│ ├─ Scene: Full scene with objects + timeline │
│ ├─ Block: Reusable component group │
│ └─ Project: Multi-scene complete project │
│ │
│ 🔧 ROBUST APPLY PIPELINE │
│ ├─ ID remapping prevents conflicts │
│ ├─ Asset resolution handles all sources │
│ └─ Timeline preservation maintains animations │
│ │
│ 💾 LAYERED STORAGE │
│ ├─ Builtin templates (shipped) │
│ ├─ User templates (IndexedDB) │
│ └─ Cloud templates (future) │
│ │
└────────────────────────────────────────────────────────────────┘