Skip to main content

PajamaScript Tool Development Guide

This guide explains how to add new tools and safely upgrade existing tools in the PajamaScript system.

Overview

PajamaScript tools add interactivity to story pages (sound effects, AR masks, background music, etc.). The system is designed for:

  • Type safety: Tools are parsed into strongly-typed Swift structs
  • Version tolerance: Tools can evolve over time while maintaining backwards compatibility
  • Performance: Tools are parsed once during JSON decode, accessed efficiently via types
  • Extensibility: Adding new tool types requires minimal changes to existing code

Architecture

Key Components

  • PajamaScriptToolParsing.swift: Generic infrastructure for all tools
  • Individual tool files: SoundEffectTool.swift, ARMaskTool.swift, etc.
  • ToolKit.swift: Container that organizes parsed tools for efficient access
  • PajamaScriptPage models: Store ToolKit instead of raw tool arrays

Tool Flow

JSON → PajamaScriptTool → [Tool].init(from:) → ToolKit → .tools(ofType:) → Usage

Adding a New Tool

1. Define the Tool Struct

Create a new file in Pajama/Core/Models/PajamaScript/ following the pattern of SoundEffectTool.swift:

struct YourTool: Codable, Equatable, Hashable, Sendable, PajamaScriptToolParseable {
static let name = "your_tool_name"

// Tool-specific properties
let someProperty: String
let version: Int

init(fromValidatedArgs args: [String: JSON], version: Int) throws {
// Version-aware parsing logic - validation is handled automatically!
}
}

2. Implement Version-Aware Parsing

Follow the pattern in SoundEffectTool.swift:

  • Implement init(fromValidatedArgs:version:) (tool name and args validation is automatic)
  • Switch on version parameter for version-specific parsing
  • Use ToolParsingError for field-specific validation
  • Store the version for debugging/analytics

3. Register in ToolKit

Add your tool to the parsing logic in ToolKit.swift:

  • Add a case in the init(from rawTools:) switch statement
  • Use YourTool.name for the case
  • Follow the same parsing pattern as existing tools

Tool Versioning and Upgrades

Version Strategy

Tools use semantic versioning where version increments indicate interface changes:

  • v1 → v2: New optional fields added
  • v2 → v3: More optional fields or behavior changes
  • Breaking changes: Require careful migration planning

Safe Upgrade Pattern

See SoundEffectTool.swift for the canonical pattern:

1. Make New Fields Optional

struct SoundEffectTool {
// v1 fields (always required)
let soundUrl: URL
let trigger: SoundEffectTrigger

// v2+ fields (optional for backwards compatibility)
let volume: Double? // v2
let fadeInMs: Int? // v2
let spatialPosition: CGPoint? // v3
}

2. Version-Specific Parsing

Add new version handlers without breaking existing ones:

switch tool.version {
case 1:
self = try Self(fromV1Args: args, version: tool.version)
case 2:
self = try Self(fromV2Args: args, version: tool.version)
case 3: // New version
self = try Self(fromV3Args: args, version: tool.version)
default:
throw ToolParsingError.unsupportedVersion(...)
}

3. Update Max Supported Version

Remember to update the maxSupported parameter in the error case when adding new versions.

Backwards Compatibility Rules

  1. Never remove required fields from earlier versions
  2. Always make new fields optional to support older tool instances
  3. Provide sensible defaults for missing optional fields
  4. Test thoroughly with tools of all supported versions

Best Practices

Error Handling

  • Use ToolParsingError enum for consistent error messages
  • Include field names, values, and version numbers in errors
  • Log warnings for unknown tool types (handled automatically by ToolKit)

Naming Conventions

  • Tool names: snake_case matching PajamaScript spec
  • Swift properties: camelCase following Swift conventions
  • File names: PascalCase + Tool.swift suffix

Performance Considerations

  • Tools are parsed once during JSON decode - keep parsing logic efficient
  • Avoid expensive operations in init(from:) - defer to usage time if needed
  • Consider caching expensive computed properties

Testing

  • Test parsing for all supported versions
  • Test error cases (missing fields, invalid values, wrong types)
  • Test round-trip encoding/decoding for disk caching
  • Include tools in your story JSON test fixtures

Key Files Reference

Core Infrastructure

  • PajamaScriptToolParsing.swift: Protocol, errors, and generic parsing utilities
  • ToolKit.swift: Tool container with type-safe access methods
  • PajamaScriptTool.swift: Raw tool representation from JSON

Example Implementation

  • SoundEffectTool.swift: Reference implementation showing best practices

Integration Points

  • PajamaScriptPage.swift: Protocol defining toolKit property
  • Page models: PajamaScriptTextPage.swift, etc. - show ToolKit integration
  • Usage examples: StoryReaderViewModel.swift, StoryContentManager.swift

PajamaScript Specification

Refer to pajamascript_spec.md for:

  • Tool JSON structure and required fields
  • Official tool names and argument specifications
  • Version requirements and compatibility guidelines

Troubleshooting

"Unknown tool type" warnings

Tools not yet implemented will be logged and stored in ToolKit.unparsedTools. This is expected during development.

Parsing errors

Check that:

  • Tool name matches the spec exactly
  • Required fields are present and correctly named
  • Field types match expectations (string vs number vs boolean)
  • Version number is supported

Build errors

Ensure:

  • New tool file is added to Xcode project
  • All imports are correct
  • PajamaScriptToolParseable protocol is implemented
  • Tool is registered in ToolKit.swift

Performance issues

If tool parsing becomes slow:

  • Profile the init(from:) method
  • Consider lazy evaluation for expensive operations
  • Avoid redundant validation or computation