Actions

Detailed specification of the actions system and built-in actions in Pathway.

Actions are TypeScript functions that define what happens when users interact with slide elements. They receive a constrained context and validated parameters, enabling safe and predictable behavior while keeping implementation flexible.

Actions run when events occur (for example, a block click or a slide lifecycle event). See Events for how events are attached to blocks and slides.

Core Concepts

  • Actions are defined using the defineAction() helper from @pathway/engine
  • Each action has a unique ID (e.g., "core.next", "core.link")
  • Parameters are validated with Zod schemas before execution
  • Actions receive an ActionContext with a constrained API (no raw DOM access)
  • All actions are registered in src/actions/registry.ts for runtime lookup

Events reference actions by ID and can include optional params (action-specific) and optional conditions. See Events.

Action Anatomy

import { defineAction } from "@pathway/engine";
import { z } from "zod";

export const myAction = defineAction({
  id: "namespace.actionName",
  schema: z.object({
    param1: z.string(),
    param2: z.number().optional(),
  }),
  handler: (context, params) => {
    // Action logic here
    // context provides: goToSlide(), getOrderedSlides(), openExternal()
  },
});

ActionContext API

The context object passed to all action handlers provides these methods:

  • goToSlide(slideId: string): Navigate to a specific slide
  • getOrderedSlides(): Get the ordered list of slide IDs
  • openExternal(url: string): Open an external URL in a new tab
  • setVariable(name, value): Set a runtime variable
  • getVariable(name): Read a runtime variable
  • setState(blockId, state): Show, hide, or disable a block

Future additions:

  • playAudio(blockId): Start audio playback
  • pauseAudio(blockId): Pause audio playback
  • seekAudio(blockId, timestamp): Jump to a specific time

Built-in Actions (Implemented)

core.next

Navigate to the next slide in sequence.

Parameters: None

Schema:

z.object({});

Behavior:

  • Gets the ordered list of slides
  • Finds the current slide position
  • Navigates to the next slide
  • Does nothing if already on the last slide

Example usage (JSON):

{
  "id": "next-btn",
  "kind": "button",
  "text": "Next",
  "click": [{ "action": "core.next" }]
}

core.prev

Navigate to the previous slide in sequence.

Parameters: None

Schema:

z.object({});

Behavior:

  • Gets the ordered list of slides
  • Finds the current slide position
  • Navigates to the previous slide
  • Does nothing if already on the first slide

Example usage (JSON):

{
  "id": "prev-btn",
  "kind": "button",
  "text": "Previous",
  "click": [{ "action": "core.prev" }]
}

Open an external URL in a new browser tab.

Parameters:

  • href (string, required): The URL to open

Schema:

z.object({
  href: z.string().url(),
});

Behavior:

  • Validates the URL format
  • Opens the URL in a new tab via context.openExternal()

Example usage (JSON):

{
  "id": "learn-more",
  "kind": "button",
  "text": "Learn More",
  "click": [
    {
      "action": "core.link",
      "params": {
        "href": "https://example.com"
      }
    }
  ]
}

Planned Built-in Actions

core.goTo

Navigate to a specific slide by ID.

Planned parameters:

  • slideId (string, required): The ID of the target slide

Planned schema:

z.object({
  slideId: z.string(),
});

Example usage:

{
  "id": "jump-btn",
  "kind": "button",
  "text": "Go to Summary",
  "click": [
    {
      "action": "core.goTo",
      "params": {
        "slideId": "summary"
      }
    }
  ]
}

core.showBlock / core.hideBlock / core.disableBlock

Control the visibility and interactivity of blocks on the current slide.

Planned parameters:

  • blockId (string or array, required): ID(s) of the block(s) to affect

Planned schema:

z.object({
  blockId: z.union([z.string(), z.array(z.string())]),
});

Use cases:

  • Progressive disclosure of content
  • Show/hide hints or feedback
  • Disable submit buttons after use
  • Toggle visibility of layers

Example usage:

{
  "id": "reveal-btn",
  "kind": "button",
  "text": "Show Answer",
  "click": [
    {
      "action": "core.showBlock",
      "params": {
        "blockId": "answer-layer"
      }
    }
  ]
}

core.showLayer / core.hideLayer

Specifically control layer visibility (specialized container blocks).

Planned parameters:

  • layerId (string, required): ID of the layer to show/hide

Use cases:

  • Tooltips
  • Modal dialogs
  • Overlays
  • Pop-up feedback

core.setVariable / core.incrementVariable / core.decrementVariable

Manage runtime variables for tracking state, scores, or progress.

Planned parameters:

  • name (string, required): Variable name
  • value (any, required for set): Value to set
  • amount (number, optional for inc/dec): Amount to change by (default: 1)

Planned schema (setVariable):

z.object({
  name: z.string(),
  value: z.union([z.string(), z.number(), z.boolean()]),
});

Use cases:

  • Quiz scoring
  • Progress tracking
  • Conditional content based on user choices
  • Module state management

Example usage:

{
  "id": "correct-answer",
  "kind": "button",
  "text": "A",
  "click": [
    {
      "action": "core.incrementVariable",
      "params": {
        "name": "score",
        "amount": 10
      }
    }
  ]
}

core.playAudio / core.pauseAudio / core.stopAudio

Control audio playback for audio blocks.

Planned parameters:

  • blockId (string, required): ID of the audio block
  • timestamp (number, optional): Jump to specific time (for play/seek)

Planned schema:

z.object({
  blockId: z.string(),
  timestamp: z.number().optional(),
});

Use cases:

  • Narration control
  • Background music
  • Sound effects
  • Audio-synchronized content

core.playVideo / core.pauseVideo / core.stopVideo

Control video playback for video blocks.

Planned parameters:

  • blockId (string, required): ID of the video block
  • timestamp (number, optional): Jump to specific time

Use cases:

  • Video-based learning content
  • Demonstrations
  • Interactive video experiences

core.showTooltip / core.showModal

Display overlay content (tooltips or modals).

Planned parameters:

  • blockId (string, required): ID of the tooltip/modal block to display
  • position (string, optional): Override positioning

Use cases:

  • Contextual help
  • Additional information
  • Feedback messages
  • Confirmations

core.submitQuiz

Submit quiz answers and evaluate results.

Planned parameters:

  • quizId (string, required): ID of the quiz block
  • answers (object, required): User’s answer selections

Planned schema:

z.object({
  quizId: z.string(),
  answers: z.record(z.any()),
});

Use cases:

  • Knowledge checks
  • Assessments
  • Interactive exercises
  • SCORM/LMS result reporting

core.triggerAnimation

Apply a CSS animation or transition to a block.

Planned parameters:

  • blockId (string, required): ID of the block to animate
  • animation (string, required): Animation type (e.g., "fadeIn", "slideUp")
  • duration (number, optional): Animation duration in milliseconds

Planned schema:

z.object({
  blockId: z.string(),
  animation: z.enum(["fadeIn", "fadeOut", "slideUp", "slideDown", "scale"]),
  duration: z.number().optional(),
});

Use cases:

  • Visual feedback
  • Emphasis on important content
  • Smooth transitions
  • Engaging interactions

Conditional Actions (Planned)

core.ifCondition

Execute different actions based on variable values or conditions.

Planned parameters:

  • condition (object, required): Condition to evaluate
  • thenAction (string, required): Action ID to execute if true
  • elseAction (string, optional): Action ID to execute if false

Planned schema:

z.object({
  condition: z.object({
    variable: z.string(),
    operator: z.enum(["eq", "neq", "gt", "lt", "gte", "lte"]),
    value: z.any(),
  }),
  thenAction: z.string(),
  thenParams: z.record(z.any()).optional(),
  elseAction: z.string().optional(),
  elseParams: z.record(z.any()).optional(),
});

Use cases:

  • Branching based on quiz performance
  • Adaptive content
  • Personalized learning paths

Global Events (Planned)

Events that trigger automatically without user interaction.

onSlideStart

Triggered when a slide becomes active.

Use cases:

  • Auto-play narration
  • Initialize slide state
  • Track analytics
  • Start timers

onSlideEnd

Triggered when leaving a slide.

Use cases:

  • Save progress
  • Clean up resources
  • Track completion

Custom Actions

Users can define custom actions by:

  1. Creating a TypeScript file in src/actions/
  2. Using defineAction() with a unique ID
  3. Registering it in src/actions/registry.ts

Example custom action:

// src/actions/custom.ts
import { defineAction } from "@pathway/engine";
import { z } from "zod";

export const logMessage = defineAction({
  id: "custom.log",
  schema: z.object({
    message: z.string(),
  }),
  handler: (context, params) => {
    console.log("[Custom]:", params.message);
  },
});
// src/actions/registry.ts
import { logMessage } from "./custom";

export const actionRegistry = new Map([
  // ... existing actions
  [logMessage.id, logMessage],
]);

Design Principles

  1. Type Safety: Zod validation ensures parameters are correct before execution
  2. Constrained Context: Actions cannot manipulate DOM directly, ensuring consistency
  3. Composability: Actions can be combined (future: action sequences)
  4. Framework-Agnostic: Action definitions live in packages/engine/
  5. Extensibility: Custom actions follow the same pattern as built-in ones
  6. Predictability: Pure functions with well-defined side effects

Visual Editor Integration

In the planned visual editor:

  • Action dropdown shows all registered actions
  • Form fields are auto-generated from Zod schemas
  • Parameters that reference blocks show a picker UI
  • Validation happens before saving

Implementation Status

  • Implemented: core.next, core.prev, core.link
  • 🚧 Planned: All other actions listed in this document

Next Steps

  1. Implement block visibility actions (showBlock, hideBlock, disableBlock)
  2. Add variable management actions (setVariable, incrementVariable)
  3. Implement media control actions (audio/video playback)
  4. Design conditional action system
  5. Add global slide events (onSlideStart, onSlideEnd)
  6. Create SCORM/LMS integration actions