npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pie-players/pie-assessment-toolkit

v0.3.29

Published

PIE assessment toolkit: composable services + reference implementation for assessment players and tool coordination

Readme

PIE Assessment Toolkit

Independent, composable services for coordinating tools, accommodations, and item players in assessment applications.

This is not an opinionated framework or monolithic "player" - it's a toolkit that solves specific problems through centralized service management.

What's New: ToolkitCoordinator

Centralized Service Management: The new ToolkitCoordinator provides a single entry point for all toolkit services, simplifying initialization and configuration.

Before (scattered services):

// Create 5+ services independently
const ttsService = new TTSService();
const toolCoordinator = new ToolCoordinator();
const highlightCoordinator = new HighlightCoordinator();
const catalogResolver = new AccessibilityCatalogResolver([...]);
// Missing: ElementToolStateStore

await ttsService.initialize(new BrowserTTSProvider());
ttsService.setCatalogResolver(catalogResolver);

// Pass all services separately
player.ttsService = ttsService;
player.toolCoordinator = toolCoordinator;
// ...

After (coordinator orchestrates):

// Create one coordinator with configuration
const toolkitCoordinator = new ToolkitCoordinator({
  assessmentId: 'my-assessment',
  tools: {
    providers: {
      textToSpeech: { enabled: true, backend: 'browser' },
      calculator: { enabled: true }
    },
    placement: {
      section: ['calculator', 'graph', 'periodicTable', 'protractor', 'lineReader', 'ruler'],
      item: ['calculator', 'textToSpeech', 'answerEliminator'],
      passage: ['textToSpeech']
    }
  }
});

// Pass single coordinator to player
player.toolkitCoordinator = toolkitCoordinator;

What Does It Solve?

  • Centralized service management: One coordinator owns all toolkit services
  • Tool coordination: z-index management, visibility state, element-level state
  • Accommodation support: IEP/504 tool configuration logic
  • TTS + annotation coordination: Prevent conflicts between highlights
  • Event communication: Standard contracts between components
  • Accessibility theming: Consistent high-contrast, font sizing
  • State separation: Ephemeral tool state separate from persistent session data

Instrumentation and Observability

Toolkit instrumentation is provider-agnostic and additive. It uses the shared InstrumentationProvider contract from @pie-players/pie-players-shared.

Injection Path

When toolkit is hosted by section/assessment player flows, the canonical provider path is the item-player loader config:

  • runtime.player.loaderConfig.instrumentationProvider

Semantics

  • With trackPageActions: true, missing/undefined providers use the default New Relic provider path.
  • instrumentationProvider: null explicitly disables instrumentation.
  • Invalid provider objects are ignored (optional debug warning), also no-op.
  • Existing item-player behavior remains the compatibility anchor.
  • Debug overlays can consume the same stream by composing providers with CompositeInstrumentationProvider (for example New Relic + debug panel).
  • Toolkit telemetry forwarding uses the same provider path, so tool/backend instrumentation is sent to production providers and is visible in debug panel overlays.

Toolkit-Owned Canonical Event Stream

  • pie-toolkit-runtime-owned
  • pie-toolkit-runtime-inherited
  • pie-toolkit-ready
  • pie-toolkit-section-ready
  • pie-toolkit-runtime-error

Toolkit tool/backend operational stream:

  • pie-tool-init-start|success|error
  • pie-tool-backend-call-start|success|error
  • pie-tool-library-load-start|success|error

Ownership boundary: toolkit emits toolkit lifecycle semantics only. Section and assessment semantic streams stay in their own layers to avoid overlap. Bridge dedupe is a safety net, not a substitute for clear ownership.

Architecture Overview

See ToolkitCoordinator Architecture for complete design documentation.

Core Principles

  1. Centralized Coordination: ToolkitCoordinator orchestrates all services
  2. Composable Services: Import only what you need (or use coordinator for convenience)
  3. No Framework Lock-in: Works with any JavaScript framework
  4. Product Control: Products control navigation, persistence, layout, backend
  5. Standard Contracts: Well-defined event types for component communication
  6. Element-Level Granularity: Tool state tracked per PIE element, not per item
  7. State Separation: Tool state (ephemeral) separate from PIE session data (persistent)

Quick Start

Option 1: Use ToolkitCoordinator (Recommended)

import { ToolkitCoordinator } from '@pie-players/pie-assessment-toolkit';

// Create coordinator with configuration
const coordinator = new ToolkitCoordinator({
  assessmentId: 'demo-assessment',
  tools: {
    providers: {
      textToSpeech: { enabled: true, backend: 'browser', defaultVoice: 'en-US' },
      calculator: { enabled: true }
    },
    placement: {
      section: ['calculator', 'graph', 'periodicTable', 'protractor', 'lineReader', 'ruler'],
      item: ['calculator', 'textToSpeech', 'answerEliminator'],
      passage: ['textToSpeech']
    }
  },
  accessibility: {
    catalogs: assessment.accessibilityCatalogs || [],
    language: 'en-US'
  }
});

// Pass to section player
const player = document.getElementById('player');
player.toolkitCoordinator = coordinator;

// Access services directly if needed
const ttsService = coordinator.ttsService;
const toolState = coordinator.elementToolStateStore.getAllState();

Controller Event Subscriptions (Helper First)

For host-side session/progress logic, prefer helper subscriptions over the generic filter API:

const unsubscribeItem = coordinator.subscribeItemEvents({
  sectionId: 'section-1',
  attemptId: 'attempt-1',
  itemIds: ['item-1', 'item-2'],
  listener: (event) => {
    // item-selected, item-session-data-changed, item-complete-changed, ...
  }
});

const unsubscribeSection = coordinator.subscribeSectionLifecycleEvents({
  sectionId: 'section-1',
  attemptId: 'attempt-1',
  listener: (event) => {
    // section-loading-complete, section-items-complete-changed, section-error, ...
  }
});

// cleanup
unsubscribeItem?.();
unsubscribeSection?.();

Use subscribeSectionEvents(...) when you need advanced/custom filtering mixes.
Note that section-scoped events do not carry item IDs, so pairing them with itemIds filters will not match. For late subscribers, subscribeSectionLifecycleEvents(...) immediately replays section-loading-complete when the target controller runtime is already in a loaded state. For deterministic targeting in multi-attempt hosts, pass both sectionId and attemptId to helper subscriptions.

Option 2: Create Services Manually (Advanced)

import {
  TTSService,
  BrowserTTSProvider,
  ToolCoordinator,
  HighlightCoordinator,
  AccessibilityCatalogResolver,
  ElementToolStateStore
} from '@pie-players/pie-assessment-toolkit';

// Initialize each service independently
const ttsService = new TTSService();
const toolCoordinator = new ToolCoordinator();
const highlightCoordinator = new HighlightCoordinator();
const elementToolStateStore = new ElementToolStateStore();
const catalogResolver = new AccessibilityCatalogResolver([], 'en-US');

await ttsService.initialize(new BrowserTTSProvider());
ttsService.setCatalogResolver(catalogResolver);

// Pass services individually
player.ttsService = ttsService;
player.toolCoordinator = toolCoordinator;
// ...

Tool Configuration Model

The toolkit uses one canonical tools model with three concerns:

  • policy: allow/block constraints (global gates)
  • placement: where tools appear (assessment, section, item, passage, rubric, plus custom registered levels)
  • providers: provider/runtime options (calculator, textToSpeech, etc.)

Example:

tools: {
  policy: {
    allowed: ['calculator', 'textToSpeech', 'answerEliminator', 'graph', 'periodicTable'],
    blocked: ['graph']
  },
  placement: {
    assessment: [],
    section: ['calculator', 'graph', 'periodicTable', 'protractor', 'lineReader', 'ruler'],
    item: ['calculator', 'textToSpeech', 'answerEliminator'],
    passage: ['textToSpeech'],
    rubric: []
  },
  providers: {
    calculator: { authFetcher: async () => ({ apiKey: '...' }) },
    textToSpeech: { enabled: true, backend: 'browser', defaultVoice: 'en-US' }
  }
}

Scope and Lifecycle

The runtime still distinguishes between contextual (item/passage) and section-wide tools:

Tool instances use structured IDs so scope is explicit:

<toolId>:<scopeLevel>:<scopeId>[:inline]

Examples:

  • calculator:section:section-1
  • calculator:item:item-42
  • textToSpeech:passage:passage-1
  • highlighter:rubric:rubric-3

Item-Level Tools (tools.placement.item)

Tools that operate within the context of a specific question/item:

tools: {
  placement: {
    item: ['calculator', 'textToSpeech', 'answerEliminator']
  }
}

Characteristics:

  • Scope: Bound to a specific item's DOM context
  • Lifecycle: Instance created/destroyed as you navigate between items
  • State: Isolated per-item (eliminations for Q5 don't affect Q6)
  • UI Pattern: Inline buttons in question headers/toolbars
  • State Persistence: Tracked per-item in ElementToolStateStore

Available Item-Level Tools:

  • TTS (Text-to-Speech): Reads the specific question/passage text
  • Answer Eliminator: Strikes through answer choices for that question
  • Highlighter: Highlights text within the item (future)

Example Use Case: A student uses answer eliminator on Question 3 to cross out choices B and D. When they navigate to Question 4, they see fresh, uneliminated choices. When they return to Question 3, their eliminations are restored.

Section-Level Tools (tools.placement.section)

Tools that float above the entire assessment and persist across questions:

tools: {
  placement: {
    section: ['calculator', 'graph', 'periodicTable', 'protractor', 'lineReader', 'ruler', 'colorScheme']
  },
  providers: {
    calculator: {
      enabled: true,
      authFetcher: async () => { /* ... */ }
    }
  }
}

Characteristics:

  • Scope: Section-wide, shared across all questions
  • Lifecycle: Single instance initialized for entire section
  • State: Persistent (calculator history remains as you navigate)
  • UI Pattern: Draggable floating panels/overlays with z-index management
  • State Persistence: Global state maintained throughout section

Available Floating Tools:

  • Calculator: Scientific/graphing calculator with computation history
  • Graph: Graphing tool for plotting functions
  • Periodic Table: Interactive periodic table reference
  • Protractor: Angle measurement tool
  • Ruler: Linear measurement tool (metric/imperial)
  • Line Reader: Reading guide/masking overlay
  • Magnifier: Screen magnification tool
  • Color Scheme: High-contrast color adjustments

Example Use Case: A student opens the calculator on Question 2, computes 45 × 12 = 540. They navigate to Question 7, and the calculator still shows their computation history. They can reference previous calculations across multiple questions without losing context.

When to Use Each

Use item-level tools when:

  • Tool needs to read/interact with specific question content
  • State should be isolated per-question
  • Tool appears inline with the question (space-efficient)
  • Tool behavior is contextual to the current item

Use floating tools when:

  • Tool is a general-purpose utility used across multiple questions
  • State should persist across navigation
  • Tool needs independent positioning and sizing
  • Tool provides reference information or computation capability

Configuration Example

Complete example showing both types:

const coordinator = new ToolkitCoordinator({
  assessmentId: 'math-exam',
  tools: {
    placement: {
      // Contextual placement
      item: ['calculator', 'textToSpeech', 'answerEliminator'],
      passage: ['textToSpeech'],
      // Section-level utilities
      section: ['calculator', 'graph', 'periodicTable', 'protractor', 'lineReader', 'ruler', 'colorScheme']
    },
    providers: {
      calculator: {
        enabled: true,
        authFetcher: async () => {
          const response = await fetch('/api/tools/desmos/auth');
          return response.json();
        }
      },
      textToSpeech: { enabled: true, backend: 'browser' }
    }
  },
  accessibility: {
    catalogs: [],
    language: 'en-US'
  }
});

Simple Default (All Tools Enabled):

For most use cases, simply enable all available tools:

const coordinator = new ToolkitCoordinator({
  assessmentId: 'my-assessment',
  tools: {
    placement: {
      section: ['calculator', 'graph', 'periodicTable', 'protractor', 'lineReader', 'ruler'],
      item: ['calculator', 'textToSpeech', 'answerEliminator'],
      passage: ['textToSpeech']
    },
    providers: {
      calculator: { enabled: true },
      textToSpeech: { enabled: true, backend: 'browser' }
    }
  }
});

The ToolkitCoordinator handles all internal complexity (service initialization, provider management, state coordination). The only special configuration is authFetcher for Desmos calculator (optional - falls back to local calculator if not provided).

Minimal Server-Backed TTS Config

For Polly/Google server-backed TTS, the provider config supports a minimal form. Common options are defaulted so you can start with:

tools: {
  providers: {
    textToSpeech: {
      enabled: true,
      backend: 'polly'
    }
  }
}

By default, server-backed TTS resolves:

  • apiEndpoint: '/api/tts'
  • transportMode: 'pie'
  • endpointValidationMode: 'voices'

You can still set apiEndpoint explicitly when your host route is not /api/tts.

Inline TTS Speed Options

Inline TTS speed buttons are configurable via speedOptions in provider settings.

tools: {
  providers: {
    textToSpeech: {
      enabled: true,
      backend: "browser",
      settings: {
        speedOptions: [2, 1.25, 1.5] // rendered in this order
      }
    }
  }
}

speedOptions semantics:

  • Omitted or non-array: default speed buttons are shown (0.8x, 1.25x).
  • Explicit empty array ([]): hide all speed buttons.
  • Invalid-only arrays (for example ["fast", -1, 1]): fall back to defaults.
  • Valid numeric values are deduplicated and keep first-seen order.
  • 1 is excluded (normal speed is already available by toggling active speed off).

Runtime Fallback: Server TTS -> Browser TTS

When server-backed playback fails at runtime (for example 503, network outage, or synthesized asset fetch failure), TTSService now performs a one-time runtime fallback for that session:

  1. Switches provider from server-backed implementation to browser speech synthesis.
  2. Rebinds highlight callbacks to the browser provider.
  3. Retries the same speak() request once.

This keeps the inline/passage TTS controls usable during transient backend incidents without requiring host-side reconfiguration.

Telemetry emitted for observability:

  • pie-tool-runtime-fallback (fallback switch succeeded)
  • pie-tool-runtime-fallback-error (fallback switch failed)

provider.runtime.authFetcher is optional. Add it only when your host environment requires runtime auth material for TTS requests:

tools: {
  providers: {
    textToSpeech: {
      enabled: true,
      backend: 'polly',
      apiEndpoint: '/api/tts',
      provider: {
        runtime: {
          authFetcher: async () => {
            const response = await fetch('/api/tts/auth');
            return response.json();
          }
        }
      }
    }
  }
}

Custom Transport via Server Proxy (SC-style)

For custom backends that return URL assets (for example { audioContent, word }), prefer a host-owned proxy endpoint so secrets never ship to the browser.

tools: {
  providers: {
    textToSpeech: {
      enabled: true,
      backend: "server",
      serverProvider: "custom",
      transportMode: "custom",
      endpointMode: "rootPost",
      endpointValidationMode: "none",
      apiEndpoint: "/api/tts/sc",
      speedRate: "medium",
      lang_id: "en-US",
      cache: true
    }
  }
}

Recommended host boundary:

  • Browser calls local proxy (/api/tts/sc) only.
  • Proxy route reads required server env vars (no defaults) and signs/attaches auth upstream.
  • Browser never receives shared secret, API key, or signing material.

SchoolCity is used as a host-configured integration example for custom transport. Toolkit defaults still remain browser/standard providers unless the host explicitly configures custom server-backed TTS.

Test Attempt Session Adapter (pie backend)

The toolkit exposes a canonical TestAttemptSession runtime and a deterministic adapter for pie backend activity payloads from ../../kds/pie-api-aws.

import {
  mapActivityToTestAttemptSession,
  toItemSessionsRecord,
  buildActivitySessionPatchFromTestAttemptSession
} from "@pie-players/pie-assessment-toolkit";

const testAttemptSession = mapActivityToTestAttemptSession({
  activityDefinition,
  activitySession
});

// Use in section-player handoff (same item session shape as item players expect)
const itemSessions = toItemSessionsRecord(testAttemptSession);

// Host-owned backend persistence payload
const patch = buildActivitySessionPatchFromTestAttemptSession(testAttemptSession);

Integration Boundary

  • @pie-players/pie-section-player stays backend-agnostic and emits session/state changes.
  • Host applications own backend I/O to pie backend (../../kds/pie-api-aws).
  • Hosts decide persistence policy (immediate, debounced, checkpoint, submit).

Section session API (controller + persistence)

For section-level session flows, the toolkit supports two complementary APIs:

  • Persistence hook: createSectionSessionPersistence(context, defaults) for load/save/clear orchestration
  • Direct controller API: getSession(), applySession(session, { mode }), updateItemSession(itemId, detail)

The persistence strategy works with the same SectionControllerSessionState shape exposed by the controller, so hosts can choose bulk restore (applySession) and fine-grained updates (updateItemSession) without internal runtime coupling.

Implementation Status

✅ Core Infrastructure

  • TypedEventBus: Type-safe event bus built on native EventTarget
  • Event Types: Complete event definitions (player, tools, navigation, state, interaction)

✅ Toolkit Services

  • ToolkitCoordinator: ⭐ NEW - Centralized service orchestration
  • ElementToolStateStore: ⭐ NEW - Element-level ephemeral tool state management
  • ToolRegistry: ⭐ NEW - Registry-based tool management with QTI 3.0 PNP support
  • PNPToolResolver: ⭐ REFACTORED - QTI 3.0 Personal Needs Profile tool resolution via registry
  • ToolCoordinator: Manages z-index layering and visibility for floating tools
  • HighlightCoordinator: Separate highlight layers for TTS (temporary) and annotations (persistent)
  • TTSService: Text-to-speech with QTI 3.0 catalog support
  • AccessibilityCatalogResolver: QTI 3.0 accessibility catalog management
  • SSMLExtractor: Automatic extraction of embedded <speak> tags
  • ThemeProvider: Consistent accessibility theming

✅ QTI 3.0 Standard Access Features

  • 95+ Standardized Features: Complete QTI 3.0 / IMS AfA 3.0 accessibility features
  • 9 Feature Categories: Visual, auditory, motor, cognitive, reading, navigation, linguistic, assessment
  • Example Configurations: Illustrative PNP profile examples (low vision, dyslexia, ADHD, etc.)
  • Tool Mappings: All 12 default tools map to standard QTI 3.0 features

✅ Section Player Integration

The toolkit integrates seamlessly with the PIE Section Player:

  • Primary Interface: Section player is the main integration point
  • Default Coordinator: Creates ToolkitCoordinator automatically if not provided
  • Automatic SSML Extraction: Extracts embedded <speak> tags from passages and items
  • Catalog Lifecycle: Manages item-level catalogs automatically
  • Service Coordination: All toolkit services work together automatically

ToolkitCoordinator API

Configuration

export interface ToolkitCoordinatorConfig {
  assessmentId: string;  // Required: unique assessment identifier
  tools?: {
    policy?: {
      allowed?: string[];
      blocked?: string[];
    };
    placement?: {
      assessment?: string[];
      section?: string[];
      item?: string[];
      passage?: string[];
      rubric?: string[];
    };
    providers?: {
      textToSpeech?: {
        enabled?: boolean;
        backend?: 'browser' | 'polly' | 'google' | 'server';
        defaultVoice?: string;
        rate?: number;
      };
      calculator?: {
        enabled?: boolean;
        authFetcher?: () => Promise<Record<string, unknown>>;
      };
    };
  };
  toolRegistry?: ToolRegistry | null;
  accessibility?: {
    catalogs?: any[];
    language?: string;
  };
}

Methods

// Get all services as a bundle
const services = coordinator.getServiceBundle();
// Returns: { ttsService, toolCoordinator, highlightCoordinator, elementToolStateStore, catalogResolver }

// Tool configuration
coordinator.isToolEnabled('textToSpeech');  // Check if tool is enabled
coordinator.getToolConfig('textToSpeech');  // Get tool-specific config
coordinator.updateToolConfig('textToSpeech', { rate: 1.5 });  // Update tool config

Direct Service Access

All services are public properties for direct access:

coordinator.ttsService              // TTSService instance
coordinator.toolCoordinator         // ToolCoordinator instance
coordinator.highlightCoordinator    // HighlightCoordinator instance
coordinator.elementToolStateStore   // ElementToolStateStore instance
coordinator.catalogResolver         // AccessibilityCatalogResolver instance

ElementToolStateStore API

The ElementToolStateStore manages ephemeral tool state at the element level using globally unique composite keys.

Key Concepts

  • Global Element ID: Composite key format: ${assessmentId}:${sectionId}:${itemId}:${elementId}
  • Element-Level Granularity: State tracked per PIE element (not per item)
  • Ephemeral State: Tool state is client-only, separate from PIE session data
  • Cross-Section Persistence: State persists when navigating between sections

ID Utilities

// Generate global element ID
const globalElementId = store.getGlobalElementId(
  'demo-assessment',
  'section-1',
  'question-1',
  'mc1'
);
// Returns: "demo-assessment:section-1:question-1:mc1"

// Parse global element ID
const components = store.parseGlobalElementId(globalElementId);
// Returns: { assessmentId, sectionId, itemId, elementId }

CRUD Operations

// Set state for a tool on an element
store.setState(globalElementId, 'answerEliminator', {
  eliminatedChoices: ['choice-a', 'choice-c']
});

// Get state for a specific tool
const state = store.getState(globalElementId, 'answerEliminator');

// Get all tool states for an element
const elementState = store.getElementState(globalElementId);

// Get all states across all elements
const allState = store.getAllState();

Cleanup Operations

// Clear state for a specific element
store.clearElement(globalElementId);

// Clear state for a specific tool across all elements
store.clearTool('answerEliminator');

// Clear all elements in a specific section
store.clearSection('demo-assessment', 'section-1');

// Clear all state
store.clearAll();

Persistence Integration

// Set callback for persistence (e.g., localStorage)
store.setOnStateChange((state) => {
  localStorage.setItem('tool-state', JSON.stringify(state));
});

// Load state from persistence
const saved = localStorage.getItem('tool-state');
if (saved) {
  store.loadState(JSON.parse(saved));
}

Reactivity

// Subscribe to state changes
const unsubscribe = store.subscribe((state) => {
  console.log('State changed:', state);
});

// Unsubscribe when done
unsubscribe();

Service APIs

TTSService

const ttsService = new TTSService();

// Initialize with provider
await ttsService.initialize(new BrowserTTSProvider());

// Set catalog resolver for SSML support
ttsService.setCatalogResolver(catalogResolver);

// Playback
await ttsService.speak('Read this text', {
  catalogId: 'prompt-001',
  language: 'en-US'
});

// Controls
ttsService.pause();
ttsService.resume();
ttsService.stop();

// Settings
await ttsService.updateSettings({
  rate: 1.5,
  voice: 'Matthew'
});

ToolCoordinator

const toolCoordinator = new ToolCoordinator();

// Register tools
toolCoordinator.registerTool('calculator', 'Calculator', element);

// Manage visibility
toolCoordinator.showTool('calculator');
toolCoordinator.hideTool('calculator');
toolCoordinator.toggleTool('calculator');

// Z-index management
toolCoordinator.bringToFront(element);

// Check state
const isVisible = toolCoordinator.isToolVisible('calculator');

HighlightCoordinator

const highlightCoordinator = new HighlightCoordinator();

// TTS highlights (temporary)
highlightCoordinator.highlightTTSWord(textNode, start, end);
highlightCoordinator.highlightTTSSentence([range1, range2]);
highlightCoordinator.clearTTS();

// Annotation highlights (persistent)
const id = highlightCoordinator.addAnnotation(range, 'yellow');
highlightCoordinator.removeAnnotation(id);

AccessibilityCatalogResolver

const resolver = new AccessibilityCatalogResolver(
  assessment.accessibilityCatalogs,
  'en-US'
);

// Add item-level catalogs
resolver.addItemCatalogs(item.accessibilityCatalogs);

// Get alternative representation
const alternative = resolver.getAlternative('prompt-001', {
  type: 'spoken',
  language: 'en-US'
});

// Clear item catalogs when navigating away
resolver.clearItemCatalogs();

SSMLExtractor

const extractor = new SSMLExtractor();

// Extract from item config
const result = extractor.extractFromItemConfig(item.config);

// Update item with cleaned config
item.config = result.cleanedConfig;
item.config.extractedCatalogs = result.catalogs;

// Register with catalog resolver
catalogResolver.addItemCatalogs(result.catalogs);

Integration with Section Player

The section player provides automatic ToolkitCoordinator integration:

<pie-section-player id="player"></pie-section-player>

<script type="module">
  import { ToolkitCoordinator } from '@pie-players/pie-assessment-toolkit';

  // Create coordinator
  const coordinator = new ToolkitCoordinator({
    assessmentId: 'my-assessment',
    tools: {
      providers: { textToSpeech: { enabled: true, backend: 'browser' } },
      placement: {
        section: ['calculator', 'graph', 'periodicTable', 'protractor', 'lineReader', 'ruler'],
        item: ['calculator', 'textToSpeech', 'answerEliminator'],
        passage: ['textToSpeech']
      }
    }
  });

  // Pass to player
  const player = document.getElementById('player');
  player.toolkitCoordinator = coordinator;
  player.section = mySection;

  // Player automatically:
  // - Extracts services from coordinator
  // - Generates section ID
  // - Provides runtime context to child components
  // - Manages SSML extraction
  // - Handles catalog lifecycle
</script>

Runtime Context Contract

The toolkit now exports a shared context key used by section-player and toolkit components:

import {
  assessmentToolkitRuntimeContext,
  type AssessmentToolkitRuntimeContext
} from "@pie-players/pie-assessment-toolkit";

AssessmentToolkitRuntimeContext carries ambient orchestration dependencies that should not be prop-drilled through intermediate components:

  • toolkitCoordinator
  • toolCoordinator
  • ttsService
  • highlightCoordinator
  • catalogResolver
  • elementToolStateStore
  • assessmentId
  • sectionId

These runtime fields are expected to be present once the section-player provider is established (host-supplied coordinator or lazily created by section-player). Use explicit props/events for direct content contracts, and use runtime context for cross-cutting orchestration scope.

Standalone Sections (No Coordinator Provided)

If no coordinator is provided, the section player creates a default one:

// No coordinator provided - section player creates default
player.section = mySection;

// Internally creates:
// new ToolkitCoordinator({
//   assessmentId: 'anon_...',  // auto-generated
//   tools: {
//     providers: { textToSpeech: { enabled: true, backend: 'browser' }, calculator: { enabled: true } },
//     placement: {
//       section: ['calculator', 'graph', 'periodicTable', 'protractor', 'lineReader', 'ruler'],
//       item: ['calculator', 'textToSpeech', 'answerEliminator'],
//       passage: ['textToSpeech']
//     }
//   }
// })

Safe Custom Tool Configuration

Default behavior is now framework-owned: invalid tools/runtime initialization is handled in pie-assessment-toolkit without host try/catch.

  • Framework logs a deterministic console error prefix: [pie-framework:<kind>:<source>]
  • Framework emits a canonical framework-error event (and still emits runtime-error for compatibility)
  • Framework renders a fallback error panel instead of a blank player
  • Startup tool-config validation can surface as kind: "coordinator-init" when the owned coordinator construction path throws.

Use createToolsConfig() when you want to pre-validate and inspect diagnostics before mounting:

import {
  createPackagedToolRegistry,
  createToolsConfig,
  ToolkitCoordinator
} from "@pie-players/pie-assessment-toolkit";

const toolRegistry = createPackagedToolRegistry();
const { config, diagnostics } = createToolsConfig({
  source: "host.bootstrap",
  strictness: "error",
  toolRegistry,
  tools: {
    providers: {
      textToSpeech: { enabled: true, backend: "browser" },
      calculator: { enabled: true }
    },
    placement: {
      item: ["calculator", "textToSpeech"]
    }
  }
});

// Fail-fast default: invalid config throws at the boundary.
const coordinator = new ToolkitCoordinator({
  assessmentId: "demo-assessment",
  toolRegistry,
  tools: config,
  toolConfigStrictness: "error"
});

Notes:

  • providers.textToSpeech is the canonical TTS provider key.
  • providers.tts is rejected by the validation contract.
  • Custom tools can provide provider-level sanitizeConfig and validateConfig hooks.
  • Hosts can react to framework errors via onframework-error listeners or onFrameworkError callback prop.
  • See docs/tools-and-accomodations/framework-owned-error-handling.md for event payload and error-kind mapping details.

State Separation: Tool State vs Session Data

The toolkit enforces a clear separation between ephemeral tool state and persistent session data:

Tool State (Ephemeral - ElementToolStateStore)

Client-only, never sent to server for scoring:

{
  "demo-assessment:section-1:question-1:mc1": {
    "answerEliminator": {
      "eliminatedChoices": ["choice-b", "choice-d"]
    },
    "highlighter": {
      "annotations": [...]
    }
  }
}

Use for:

  • Answer eliminations
  • Highlighting/annotations
  • Tool preferences
  • UI state

PIE Session Data (Persistent)

Sent to server for scoring:

{
  "question-1": {
    "id": "session-123",
    "data": [
      { "id": "mc1", "element": "multiple-choice", "value": ["choice-a"] }
    ]
  }
}

Use for:

  • Student responses
  • Scoring data
  • Assessment outcomes

Examples

See the section-demos for complete examples:

  • Three Questions Demo: Element-level answer eliminator with state persistence
  • TTS Integration Demo: Toolkit coordinator with TTS service
  • Paired Passages Demo: Multi-section assessment with cross-section state

TypeScript Support

Full TypeScript definitions included:

import type {
  IToolkitCoordinator,
  IElementToolStateStore,
  ToolkitCoordinatorConfig,
  ToolkitServiceBundle
} from '@pie-players/pie-assessment-toolkit';

Related Documentation

License

MIT