Skip to content

Changelog ​

All notable changes to @ivkit/core, @ivkit/player and @ivkit/react are documented here. The packages are versioned together.

The format is based on Keep a Changelog, and this project follows Semantic Versioning. Until 1.0.0, minor versions may include breaking changes, which are always listed under Changed or Removed.

Unreleased ​

Added ​

  • @ivkit/react: a new package with an IvkitPlayer React component for React 18 and 19. It sets story, renderHotspot and plugins as properties, takes controls, autoplay and muted as boolean props, turns each event into a typed callback such as onAnswer, renders children into the player's slots and forwards its ref to the element. It's a client component, so server components can render it.

0.2.0 - 2026-10-29 ​

Variables and conditions, multiple-answer and timed questions, cues and player plugins, jumping to any scene, and recovery from videos that fail. Both packages are now licensed under Apache 2.0.

Added ​

  • @ivkit/core: answerRange(question, offered), the fewest and most options an answer takes. The engine, validateStory and the player now share it.
  • @ivkit/core: StoryEngine.goTo(sceneId, time?), which enters any scene from any state, dismissing the question on screen, and plays it from time, asking its questions from there on. It works before start(), e.g. to resume a saved story. The scene event now carries that time, and MediaController starts the video there.
  • @ivkit/core: StoryEngine.seek(time), which resumes the current scene from time like a seek in the story.
  • @ivkit/core: StoryEngine.destroy(), which cancels a question's timeout and removes every listener, and pauseTimeout() and resumeTimeout(), which stop and go on with a question's countdown.
  • @ivkit/player: goTo(sceneId, time?) on <ivkit-player>, which jumps to a scene and plays it. Use it to branch on a score, resume where the viewer left off or build chapter menus.
  • @ivkit/core: an error event on MediaController when a scene's video can't be loaded or played, and retry(), which loads it again from where it failed. A failed preload is no longer stuck: it's loaded again when its scene comes.
  • @ivkit/player: an error screen with an error slot, an iv-error event and retry(), so a missing or unplayable video no longer leaves the player frozen without notice.
  • @ivkit/player: removing the player from the page pauses a question's timeout until it's back, and keeps a scene that finishes loading meanwhile from playing. Setting another story destroys the previous engine.
  • @ivkit/core: pause on hotspots, which pauses the video when the hotspot appears until it's activated. The engine emits a new pause event, and MediaController pauses on the hotspot's first frame and resumes when it's activated without a goto.
  • @ivkit/core: variables and conditions in the story format. Story.vars declares variables, set changes them on choices, hotspots and scenes, branches pick a scene by condition before falling back to goto or next, and if offers a choice or shows a hotspot only while it holds. validateStory reports undeclared variables with the new missing_variable code.
  • @ivkit/core: StoryEngine.vars, StoryEngine.setVar() and a vars event. The interaction event now includes the options offered, and answer and hotspot include the scene they branch to as goto.
  • @ivkit/player: questions show only the options whose if holds, and iv-interaction, iv-answer and iv-hotspot carry the same new fields as the engine's events.
  • @ivkit/core: correct on choices, reported in the answer event to score quizzes.
  • @ivkit/core: seek on choices and hotspots, which resumes the current scene from another second when they don't branch, asking the questions from there on again. MediaController now seeks on resume when its time isn't the current one.
  • @ivkit/player: href on hotspots, which opens http: and https: links in a new tab when activated.
  • @ivkit/core: multiple-answer questions. multiple: true takes several options, bounded by min and max, and applies each one's set. Questions also take goto, branches and seek, which decide for multiple questions and act as a fallback for single ones. StoryEngine.answer() takes a list of ids, and the answer event adds choices and correct, true when exactly the options marked correct were picked.
  • @ivkit/player: multiple questions show toggle options and a Confirm button, exposed as ::part(submit).
  • @ivkit/core: timeout and default on questions. When nobody answers in time, the engine emits a new timeout event and answers with default, or else lets the question's branches, goto and seek decide.
  • @ivkit/player: a countdown bar for questions with timeout, exposed as ::part(timer), and an iv-timeout event. The player now ignores events from an engine it has replaced.
  • @ivkit/core: cue events in the story format, which mark a span of a scene for plugins and apps, picked by kind. The engine emits a new cues event with the active ones and exposes them as StoryEngine.activeCues.
  • @ivkit/core: data on the story, scenes, events and options, free-form JSON that IVKit carries but never reads. validateStory checks that it's a JSON object.
  • @ivkit/core: validateStory(story, { cues }) runs a plugin's checks on cues of its kind, reporting with the new invalid_cue code.
  • @ivkit/player: plugins through player.use(plugin). A plugin runs for each story with the engine, the media controller, its own layer over the video and an on() that unsubscribes by itself, and may return a cleanup. The player also dispatches iv-cues.
  • @ivkit/core: a time event on MediaController, with each playback time it reports to the engine. <ivkit-player> now moves animated hotspots on it instead of keeping its own frame callbacks.

Changed ​

  • The license is now the Apache License 2.0 instead of MIT, for both packages.
  • Size budgets: the standalone player now stays under 9.5 KiB gzip (was 8 KiB), and all of @ivkit/core under 6 KiB (was 4 KiB), to fit variables, conditions, multiple-answer questions and timeouts.
  • @ivkit/player: its CSS is now minified at build time, which takes about 500 B off the standalone player.
  • @ivkit/core: MediaController emits blocked only when the autoplay policy rejects play() (a NotAllowedError). A play() interrupted by a pause or a new source no longer shows the player's Play button over a scene that's playing.
  • @ivkit/core: MediaController.play() does nothing while the next scene loads, so the previous scene can't play, heard but hidden, behind it.
  • @ivkit/player: setting story to one whose start isn't a scene throws without resetting the player, instead of leaving it half reset.
  • @ivkit/core: a listener that moves the story, with goTo(), start() or seek(), wins over what the engine was doing. Before, a goTo() from an answer, hotspot or timeout listener was overridden by the answer's own branch or followed by a stale resume.
  • @ivkit/core: a listener that throws no longer stops the other listeners or leaves the engine halfway through a transition. Its error is still reported.
  • @ivkit/player: a plugin that throws, when it runs or cleans up, no longer stops the player or the plugins after it.
  • @ivkit/player: restart(), and so the Replay button, sets the variables back to their initial values. Before, a replay kept the previous run's scores and counters.
  • @ivkit/player: hotspots that stay on screen keep their buttons when others appear or go, so keyboard focus, renderHotspot changes and animations aren't lost. renderHotspot runs once per hotspot while it stays on screen.

0.1.1 - 2026-10-27 ​

The first release of @ivkit/player, which wasn't published as 0.1.0.

Fixed ​

  • @ivkit/core: VERSION reported 0.0.0 instead of the package's version. It's now read from package.json at build time.

0.1.0 - 2026-10-27 ​

The first release of @ivkit/core.

Added ​

@ivkit/core ​

  • Story format: scenes with a video, a next scene, questions and hotspots.
  • StoryEngine, a DOM-free state machine that drives a story from playback time, with events for scenes, questions, answers, hotspots and the end of the story.
  • Questions that pause on their exact frame and branch, resume or continue to the next scene. They fire once per visit, and seeking past several asks only the earliest.
  • Hotspots shaped as rectangles, ellipses or polygons, animated through keyframes with linear, ease-in-out or step easing, morphing between shapes.
  • MediaController, which plays a story on two stacked media elements: it preloads the likely next scene and swaps only once its first frame can be drawn, so there's no black frame between scenes. It follows playback with requestVideoFrameCallback, falling back to timeupdate.
  • validateStory, which reports every problem in untrusted story data with a JSON Pointer and an error code.
  • hotspotGeometryAt, boundsOf and isAnimated, for drawing hotspots in custom players.

@ivkit/player ​

  • The <ivkit-player> Web Component, with controls, autoplay and muted attributes.
  • A control bar with play/pause, seeking within the scene, volume and fullscreen, plus keyboard shortcuts.
  • Fullscreen for the whole player, including questions and hotspots, with a viewport-covering fallback on iPhone.
  • Hotspot variants outline, pulse, label and hidden, custom variants styled through ::part(hotspot-<variant>), and renderHotspot for custom content.
  • Theming through --iv-* custom properties, ::part() for every element, and start and end slots.
  • iv-scene, iv-interaction, iv-answer, iv-hotspot and iv-end events, typed through HTMLElementEventMap.
  • @ivkit/player/element, which exports the element class and defineIvkitPlayer() without registering anything.
  • Safe to import during server-side rendering. React Server Components, Cloudflare Workers and Vercel Edge get a server build without DOM code.
  • A standalone build, ivkit-player.min.js, that includes core and loads straight from a CDN, under an 8 KiB gzip budget.