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 accessPajamaScriptPagemodels: StoreToolKitinstead 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
versionparameter for version-specific parsing - Use
ToolParsingErrorfor 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.namefor 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
- Never remove required fields from earlier versions
- Always make new fields optional to support older tool instances
- Provide sensible defaults for missing optional fields
- Test thoroughly with tools of all supported versions
Best Practices
Error Handling
- Use
ToolParsingErrorenum 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_casematching PajamaScript spec - Swift properties:
camelCasefollowing Swift conventions - File names:
PascalCase+Tool.swiftsuffix
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 utilitiesToolKit.swift: Tool container with type-safe access methodsPajamaScriptTool.swift: Raw tool representation from JSON
Example Implementation
SoundEffectTool.swift: Reference implementation showing best practices
Integration Points
PajamaScriptPage.swift: Protocol definingtoolKitproperty- 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
PajamaScriptToolParseableprotocol 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