feat: Add Interactive API tile for embedding LARA-compatible interactives [CLUE-333]#2716
feat: Add Interactive API tile for embedding LARA-compatible interactives [CLUE-333]#2716dougmartin merged 11 commits intomasterfrom
Conversation
…ives [CLUE-333] Implements a new tile type that embeds external interactive content via iframe using the LARA Interactive API protocol. Enables curriculum authors to integrate educational interactives from question-interactives and other LARA-compatible sources directly into CLUE documents. Key features: - Full LARA Interactive API v1.x protocol support with iframe-phone - Bidirectional state management with 500ms debouncing for Firebase efficiency - Dynamic iframe height adjustment with loop prevention (100ms debounce, 5px threshold) - Separate interactiveState (student work) and authoredState (teacher config) - Read-only "report" mode and full "runtime" mode support - State polling fallback (2s interval) ensures capture without proactive messages - Configurable iframe permissions (geolocation, camera, microphone, etc.) - WCAG 2.1 AA accessibility with ARIA attributes and skip-to-content - Error boundaries for isolation and loading states with 2s delay - Comprehensive logging support forwarding interactive events Implementation: - Content model using MST frozen types for immutable state management - 447-line React component handling iframe lifecycle and protocol messages - Configurable maxHeight and enableScroll options for layout control - 27 unit and integration tests covering state, rendering, and communication Dependencies added: - @concord-consortium/lara-interactive-api for protocol definitions - iframe-phone for robust postMessage communication Documentation: - Detailed README with architecture, usage, and troubleshooting guide - Implementation spec documenting step-by-step development process - Curriculum authoring examples and developer integration guide
There was a problem hiding this comment.
Pull request overview
This PR implements a new Interactive API tile type that enables embedding LARA-compatible external interactives into CLUE documents via iframe. The tile provides full protocol support for bidirectional state management, dynamic height adjustment, and accessibility features, allowing curriculum authors to integrate educational interactives from question-interactives and other LARA-compatible sources.
Changes:
- Added new Interactive API tile plugin with complete LARA Interactive API v1.x protocol implementation
- Integrated iframe-phone for robust postMessage communication with debounced state management
- Added comprehensive documentation including implementation spec, README, and tile creation guide updates
Reviewed changes
Copilot reviewed 18 out of 21 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
tiles.md |
Added comprehensive guide for creating new tiles in CLUE with starter tile instructions |
src/register-tile-types.ts |
Registered InteractiveApi tile for dynamic loading |
src/public/demo/units/qa/investigation-0/problem-1/introduction/content.json |
Added three test Interactive API tiles for QA testing |
src/public/demo/units/qa/content.json |
Added Interactive tile to QA unit toolbar |
src/plugins/interactive-api/interactive-api-tile.tsx |
Implemented 447-line React component with iframe lifecycle and LARA protocol handling |
src/plugins/interactive-api/interactive-api-tile.test.tsx |
Added 12 unit tests for component rendering and behavior |
src/plugins/interactive-api/interactive-api-tile.scss |
Implemented styling with accessibility features and loading states |
src/plugins/interactive-api/interactive-api-tile-types.ts |
Defined tile type constants |
src/plugins/interactive-api/interactive-api-tile-registration.ts |
Registered tile content and component info |
src/plugins/interactive-api/interactive-api-tile-integration.test.tsx |
Added 4 integration tests for iframe-phone communication |
src/plugins/interactive-api/interactive-api-tile-content.ts |
Implemented MST content model with frozen state types |
src/plugins/interactive-api/interactive-api-tile-content.test.ts |
Added 15 content model unit tests |
src/plugins/interactive-api/README.md |
Added comprehensive documentation with architecture, usage, and troubleshooting |
src/lib/logger-types.ts |
Added TILE_INTERACTIVE_LOG event type for interactive logging |
src/clue/app-config.json |
Added InteractiveApi to default toolbar configuration |
package.json |
Added lara-interactive-api and iframe-phone dependencies |
docs/specs/add-interactive-api-tile.md |
Added detailed 2500-line implementation specification |
README.md |
Added reference to tile creation documentation |
Comments suppressed due to low confidence (1)
src/plugins/interactive-api/interactive-api-tile.tsx:1
- The
getInteractiveStatemessage is posted without any data parameter, but line 359 shows it being called withnull. For consistency and clarity with the LARA Interactive API protocol, always passnullexplicitly as the second parameter when no data is needed.
import React, { useEffect, useRef, useState, useCallback, useMemo } from "react";
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 21 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (1)
src/plugins/interactive-api/interactive-api-tile.tsx:1
- The
postmethod for 'getInteractiveState' is called without a second argument, but according to the iframe-phone interface definition (line 22),postexpects two parameters:(type: string, data: any). This should passnullas the second argument:phoneRef.current?.post(\"getInteractiveState\", null).
import React, { useEffect, useRef, useState, useCallback, useMemo } from "react";
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #2716 +/- ##
==========================================
- Coverage 86.70% 86.64% -0.06%
==========================================
Files 812 816 +4
Lines 43535 43754 +219
Branches 11137 11186 +49
==========================================
+ Hits 37746 37911 +165
- Misses 5451 5496 +45
- Partials 338 347 +9
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
collaborative-learning
|
||||||||||||||||||||||||||||
| Project |
collaborative-learning
|
| Branch Review |
CLUE-333-add-stormexplorer-tile
|
| Run status |
|
| Run duration | 02m 59s |
| Commit |
|
| Committer | Doug Martin |
| View all properties for this run ↗︎ | |
| Test results | |
|---|---|
|
|
0
|
|
|
0
|
|
|
0
|
|
|
0
|
|
|
4
|
| View all changes introduced in this branch ↗︎ | |
- Remove unused _iframeHeight state variable and setIframeHeight call - Fix closure staleness in loading timeout by removing unnecessary isLoading check - Improve type safety by renaming baseProps to commonProps and moving linkedInteractives into mode-specific branches - Fix iframe-phone reconnection loop by using contentRef for authoredState and interactiveState - Remove unsafe type casting in keyboard event handler by implementing inline handler
Fix test isolation issues in interactive-api-tile.test.tsx by creating fresh content and model instances for each test via createTestProps() helper function. This prevents state leakage between tests where URL changes in one test would affect subsequent tests. Fix iframe-phone mock setup in interactive-api-tile-integration.test.tsx by moving jest.mock() call before component import, ensuring the mock is properly applied. Update polling test to account for initial setup calls and fix state update test to properly trigger registered listeners. All 17 tests now pass successfully.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 21 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (2)
src/plugins/interactive-api/interactive-api-tile.tsx:1
- The
iframeHeightstate variable is set but never used. The component doesn't apply this state to the iframe or container, making it a dead write. Either remove this state variable and thesetIframeHeightcall, or apply it to control the iframe's height.
import React, { useEffect, useRef, useState, useCallback, useMemo } from "react";
src/plugins/interactive-api/interactive-api-tile.tsx:1
- The onKeyDown handler on line 593 in the spec differs from the implementation shown in the diff (lines 397-404). The spec version uses a concise inline handler
onKeyDown={(e) => e.key === 'Enter' && handleSkipToContent(e)}, while the diff shows a more verbose version that properly callspreventDefault(). The spec's inline version doesn't callpreventDefault(), which means pressing Enter would both trigger the handler AND follow the href. The diff's implementation is correct. This discrepancy suggests the spec documentation is outdated.
import React, { useEffect, useRef, useState, useCallback, useMemo } from "react";
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 21 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (2)
src/plugins/interactive-api/interactive-api-tile.tsx:1
- The iframeHeight state is declared but never used. The component calls setIframeHeight in handleHeight (line 369) but the state value is not applied anywhere in the render. Consider removing this unused state or integrating it with the height management logic.
/*
src/plugins/interactive-api/interactive-api-tile.tsx:1
- The getInteractiveState polling passes null as the second argument, but according to the spec documentation (line 555 in the spec file), it should be called without any arguments. Consider removing the null parameter for consistency with the documented approach.
/*
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 18 out of 21 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (3)
src/plugins/interactive-api/interactive-api-tile.tsx:1
- The event handler type in handleSkipToContent is too restrictive. The onKeyDown handler at line 494 passes a React.KeyboardEvent, but this function only accepts React.MouseEvent. Change the parameter type to React.SyntheticEvent to accept both mouse and keyboard events.
/*
src/plugins/interactive-api/interactive-api-tile.tsx:1
- The getInteractiveState message should be sent with null as the second parameter for consistency with the LARA protocol, as shown in the spec at line 437. Change to phoneRef.current?.post('getInteractiveState', null).
/*
src/plugins/interactive-api/interactive-api-tile.tsx:1
- The inline event handler will fail when Enter is pressed because handleSkipToContent expects React.MouseEvent but receives React.KeyboardEvent. Extract this logic to a separate handler or update handleSkipToContent to accept React.SyntheticEvent.
/*
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
scytacki
left a comment
There was a problem hiding this comment.
Great set of code. I requested a few changes. Feel free to argue about them. :)
Code changes following file renames in previous commit: - Rename tile type from InteractiveApi to IframeInteractive throughout codebase - Move allowedPermissions from tile content model to unit-level settings for improved security (prevents runtime modification by users) - Rename log event from TILE_INTERACTIVE_LOG to IFRAME_INTERACTIVE_TOOL_CHANGE to follow naming conventions - Remove tile from global app-config.json toolbar (should be enabled per-unit) - Update test tile IDs in QA content.json to use iframe-interactive prefix Documentation updates: - tiles.md: Remove incorrect steps about tile-content-union.ts (auto-generated) - tiles.md: Add Step 5 for unit configuration best practices - tiles.md: Fix example references to use starter tile instead of bar-graph - starter/README.md: Add instructions for unit configuration - iframe-interactive/README.md: Document unit-level permissions configuration - add-interactive-api-tile.md: Add note explaining the rename for historical context
scytacki
left a comment
There was a problem hiding this comment.
Looks good. I just left one last nit pick comment.
Implements a new tile type that embeds external interactive content via iframe using the LARA Interactive API protocol. Enables curriculum authors to integrate educational interactives from question-interactives and other LARA-compatible sources directly into CLUE documents.
Key features:
Implementation:
Dependencies added:
Documentation: