Files
gnommoplayer/deployment.md
T
2026-04-11 09:21:22 +02:00

17 KiB
Raw Blame History

Deploying GlitchPlayer Into Another React + Vite App

This guide explains how to include GlitchPlayer inside another user interface that is also built with React and Vite.

The target mental model is:

  • your host app owns routing, fetching, auth, persistence, and editor UI
  • GlitchPlayer owns presentation playback, responsive layout, slide rendering, gesture handling, pause/resume behavior, and voting UI

In other words, GlitchPlayer should behave like an embeddable runtime surface inside a larger product such as GnommoEditor.

For editor integrations, this package also exposes:

  • GlitchSlideRenderer
  • GlitchSlideThumbnail

For template pickers, prefer the normalized availability exports:

  • slideTemplateAvailabilityList
  • slideTemplateAvailabilityByDisplayMode

These expand templates by display mode. If a template supports both square and fullscreen, it appears once for each supported format.

1. What The Host App Must Provide

To render GlitchPlayer, the host app needs to provide:

  • a presentation object
  • a slideRegistry
  • an optional player mode
  • an optional target slide id
  • an optional vote handler
  • an optional player event handler
  • an optional session id

Today, the public player surface is exported from:

The primary component is:

  • GlitchPlayer
  • GlitchSlideRenderer
  • GlitchSlideThumbnail

The key types are:

  • PresentationDefinition
  • SlideRegistry
  • VoteRequest
  • ClientEvent

For editor-driven slide forms and template pickers, this package also exports:

  • slideTemplateDefinitions
  • slideTemplateDefinitionList
  • slideTemplateAvailabilityList
  • slideTemplateAvailabilityByDisplayMode
  • slideTemplateDrafts
  • slideTemplateBudgets

For hosted interactive Glitch components, the player also supports a base URL convention:

  • VITE_GLITCH_COMPONENT_BASE_URL

If a GlitchComponentFrame slide omits componentUrl, the player resolves the iframe source as:

  • ${VITE_GLITCH_COMPONENT_BASE_URL || "/glitch"}/glitch_${componentId.replace(/-/g, "_")}/index.html

That matches the pattern where built Glitch component assets are copied into public/glitch/....

2. Public API

Current host usage looks like this:

import {
  GlitchPlayer,
  GlitchSlideThumbnail,
  createSessionId,
  defaultSlideRegistry,
  type PresentationDefinition,
  type Slide,
  type VoteRequest,
  type ClientEvent
} from "gnommoplayer";
import "gnommoplayer/player/styles.css";

interface PlayerHostProps {
  presentation: PresentationDefinition;
}

export function PlayerHost({ presentation }: PlayerHostProps) {
  async function handleVoteCommit(vote: VoteRequest) {
    await fetch(`/api/presentations/${presentation.id}/votes`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(vote)
    });
  }

  function handlePlayerEvent(event: ClientEvent) {
    console.log("[glitch-player-event]", event);
  }

  return (
    <GlitchPlayer
      presentation={presentation}
      slideRegistry={defaultSlideRegistry}
      mode="playback"
      initialSessionId={createSessionId()}
      onVoteCommit={handleVoteCommit}
      onEvent={handlePlayerEvent}
    />
  );
}

2.1 Rendering Slide Thumbnails In An Editor

Use GlitchSlideThumbnail for slide lists, cards, and inspector previews. It renders the actual slide component without video playback or player chrome.

function SlideCard({
  presentationId,
  slide,
}: {
  presentationId: string;
  slide: Slide;
}) {
  return (
    <GlitchSlideThumbnail
      presentationId={presentationId}
      slide={slide}
      slideRegistry={defaultSlideRegistry}
      viewportMode="desktop"
    />
  );
}

Use GlitchSlideRenderer when you want a larger slide-only render surface without the surrounding player.

2.1 Previewing A Specific Slide

For editor workflows, GlitchPlayer can start in a slide-targeted preview mode.

Use:

  • mode="slide-preview"
  • targetSlideId="some-slide-id"

Example:

<GlitchPlayer
  presentation={presentation}
  slideRegistry={defaultSlideRegistry}
  mode="slide-preview"
  targetSlideId="slide-05-chart-single"
  initialSessionId={createSessionId()}
/>

That mode is useful when:

  • an editor wants to preview one slide in context
  • a template author wants to inspect a specific candidate
  • GnommoEditor wants to jump directly to the currently selected slide in the timeline

3. Installation Strategy

There are two practical ways to use GlitchPlayer inside another Vite app.

Option A: Local workspace dependency

Use this when the host app and gnommoplayer live side-by-side during active development.

Example:

{
  "dependencies": {
    "gnommoplayer": "file:../gnommoplayer"
  }
}

Then install as usual:

npm install

This is the easiest path while GlitchPlayer is still evolving rapidly.

Option B: Internal package

Use this when the player should be versioned and consumed more formally.

That usually means:

  • adding a proper package export map
  • producing a distributable build
  • publishing to a private npm registry or workspace package feed

This repo is not fully packaged for that yet, but the code is now organized in a way that makes that a straightforward next step.

Option C: Built Files Only

Use this when another agent or deployment process is only allowed to fetch compiled assets from this repo.

Build the vendor assets with:

npm run build:lib

Or simply run:

npm run build

which now builds both the demo app and the vendor assets in one pass.

That produces these stable files:

  • dist/vendor/gnommoplayer.js
  • dist/vendor/gnommoplayer.css

This is the best path for GnommoEditor if the integration process is only allowed to pull built artifacts rather than source files.

3.1 Local Deployment Commands

This repo includes a deployment helper that copies the latest built vendor bundle into sibling apps.

Available commands:

npm run deploy:editor
npm run deploy:web
npm run deploy:all
./deploy.sh editor
./deploy.sh web
./deploy.sh all

The shell wrapper is useful when you just want a short deploy command from the repo root:

./deploy.sh

That defaults to:

npm run deploy:all

The npm commands are still available too:

npm run deploy:editor
npm run deploy:web
npm run deploy:all

These commands:

  • run npm run build:lib
  • ensure the vendor artifacts exist
  • copy the latest bundle into the target app

Current target paths:

  • ../gnommoeditor/src/vendor/gnommoplayer
  • ../gnommoweb/src/vendor/gnommoplayer

The underlying script lives at:

You can also call it directly:

node scripts/deploy-vendor.mjs editor
node scripts/deploy-vendor.mjs web
node scripts/deploy-vendor.mjs all
node scripts/deploy-vendor.mjs all --skip-build

Use --skip-build only when you know dist/vendor/ already contains the bundle you want to publish.

4. Styling Requirements

Import the player stylesheet once in the host app:

import "gnommoplayer/player/styles.css";

If the host app is consuming built files directly instead of the source package, import the compiled vendor assets instead:

import "../vendor/gnommoplayer/gnommoplayer.css";
import {
  GlitchPlayer,
  GlitchSlideThumbnail,
  defaultSlideRegistry
} from "../vendor/gnommoplayer/gnommoplayer.js";

Inside this repo, that stylesheet entry currently lives at:

Important notes:

  • The player expects to own its own visual surface.
  • The host should avoid wrapping it in containers that impose additional padding, border radius, or clipping unless that is intentional.
  • The player uses viewport-aware layout logic and expects enough room to center its stage within the available host surface.

Recommended host wrapper:

export function PlayerScreen({ children }: { children: React.ReactNode }) {
  return (
    <div
      style={{
        width: "100%",
        minHeight: "100vh",
        background: "#6c6c6c"
      }}
    >
      {children}
    </div>
  );
}

If the host app has its own page chrome, toolbars, or inspector panels, mount the player inside a dedicated content region rather than inside an arbitrary card component.

4.1 Built-File Vendor Workflow

For a sibling React + Vite app that is only allowed to fetch built files, the cleanest integration flow is:

  1. Run npm run build:lib in gnommoplayer
  2. Copy:
    • dist/vendor/gnommoplayer.js
    • dist/vendor/gnommoplayer.css
  3. Place them in a vendor folder inside the host app, for example:
    • src/vendor/gnommoplayer/gnommoplayer.js
    • src/vendor/gnommoplayer/gnommoplayer.css
  4. Import the CSS once near the app root
  5. Import the JS module where the host renders the player or thumbnails

The built JS bundle already inlines the dynamic slide imports, so the host does not need to manage extra chunk files for the standard slide set.

5. Presentation Data Contract

GlitchPlayer expects a PresentationDefinition.

That type is currently defined in:

At the moment, PresentationDefinition is an alias for the existing internal LectureDefinition shape.

The important fields are:

interface PresentationDefinition {
  id: string;
  title: string;
  version: string;
  durationSec: number;
  segments: VideoSegment[];
  slides: Slide[];
}

Each presentation needs:

  • ordered video segments
  • timed slides
  • one or more glitchSlides per logical slide
  • valid componentKey values that exist in the registry supplied by the host

6. Loading Presentations In The Host App

The host app should fetch presentation data before rendering GlitchPlayer.

Example host loader:

import { useEffect, useState } from "react";
import { GlitchPlayer, type PresentationDefinition } from "gnommoplayer";

export function PresentationRoute({
  presentationId
}: {
  presentationId: string;
}) {
  const [presentation, setPresentation] =
    useState<PresentationDefinition | null>(null);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    let cancelled = false;

    fetch(`/api/presentations/${presentationId}`)
      .then(async (response) => {
        if (!response.ok) {
          throw new Error(`Failed to load presentation ${presentationId}`);
        }

        return (await response.json()) as PresentationDefinition;
      })
      .then((data) => {
        if (!cancelled) {
          setPresentation(data);
        }
      })
      .catch((caughtError: unknown) => {
        if (!cancelled) {
          setError(
            caughtError instanceof Error
              ? caughtError.message
              : "Could not load presentation."
          );
        }
      });

    return () => {
      cancelled = true;
    };
  }, [presentationId]);

  if (error) {
    return <div>{error}</div>;
  }

  if (!presentation) {
    return <div>Loading</div>;
  }

  return (
    <GlitchPlayer
      presentation={presentation}
      slideRegistry={defaultSlideRegistry}
    />
  );
}

7. Slide Registry Integration

The registry maps componentKey strings from presentation data to lazily-loaded React modules.

It looks like this:

type SlideRegistry = Record<
  string,
  () => Promise<{ default: React.ComponentType<SlideRuntimeProps> }>
>;

In this repo, the built-in demo registry lives at:

Example host registry:

import type { SlideRegistry } from "gnommoplayer";

export const gnommoEditorSlideRegistry: SlideRegistry = {
  SquareYellow: () => import("./slides/SquareYellow"),
  FullscreenSplit: () => import("./slides/FullscreenSplit"),
  RevenueChart: () => import("./slides/RevenueChart"),
  OrbitalModel3D: () => import("./slides/OrbitalModel3D")
};

Rules to keep in mind:

  • Every componentKey referenced by the presentation must exist in the registry.
  • Components should be deterministic and reasonably lightweight.
  • Prefer componentKey + props over arbitrary runtime code from a database.
  • If the host supports custom submitted code, that code should be reviewed or sandboxed before entering the public registry.

8. Vote Handling

The player can emit vote requests through onVoteCommit.

That callback receives a VoteRequest:

interface VoteRequest {
  lectureId: string;
  slideId: string;
  previousGlitchSlideId: string;
  selectedGlitchSlideId: string;
  voteDirection?: "up" | "down";
  sessionId: string;
  timestamp: string;
  deviceContext: DeviceContext;
}

Even though the field is still named lectureId internally, treat it as the presentation id for integration purposes.

Recommended host behavior:

  • send the vote request to your own API
  • include auth/session context on the server side
  • do not block the UI longer than necessary
  • log failures, but keep playback usable

Example:

async function handleVoteCommit(vote: VoteRequest) {
  const response = await fetch(`/api/presentations/${vote.lectureId}/votes`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(vote)
  });

  if (!response.ok) {
    throw new Error("Vote request failed.");
  }
}

9. Player Events

The host can also listen to runtime events via onEvent.

Current events include things like:

  • lecture_loaded
  • segment_changed
  • slide_activated
  • soft_pause_started
  • soft_pause_ended
  • glitch_slide_changed
  • vote_committed

This is useful for:

  • analytics
  • debugging
  • editor preview tooling
  • QA capture

Example:

function handlePlayerEvent(event: ClientEvent) {
  if (event.type === "slide_activated") {
    console.log("Active slide:", event.slideId, event.glitchSlideId);
  }
}

For a host app like GnommoEditor, a strong integration pattern is:

  • app shell owns toolbar, navigation, inspector, and editor panels
  • central preview region owns the player mount
  • the player fills that preview region

Example:

export function EditorPreviewPane({
  presentation
}: {
  presentation: PresentationDefinition;
}) {
  return (
    <section
      style={{
        flex: 1,
        minWidth: 0,
        minHeight: 0,
        background: "#5d5d5d"
      }}
    >
      <GlitchPlayer
        presentation={presentation}
        slideRegistry={defaultSlideRegistry}
      />
    </section>
  );
}

Important:

  • make sure the containing region allows height to collapse properly with min-height: 0 when used inside flex or grid shells
  • avoid nesting inside containers with accidental overflow: hidden unless intended
  • avoid wrapping the player in padded cards, because the player already frames its own stage

11. Vite Considerations

If the host app is also using Vite:

  • dynamic import() for slide components will work well
  • CSS import from the player should be straightforward
  • local workspace dependency is usually the easiest during development

Potential issues to watch:

  • duplicated React versions between host and player
  • CSS ordering conflicts if the host imports many global styles after the player stylesheet
  • path alias assumptions if you later move this from repo-local import to packaged dependency

Recommended safeguards:

  • keep React and ReactDOM deduped in the workspace
  • import player styles at the app shell level
  • keep slide modules in the host app, not hidden inside the player package, if GnommoEditor should own template evolution

12. Minimal GnommoEditor Integration Plan

If the target is GnommoEditor, the simplest inclusion path is:

  1. Add gnommoplayer as a workspace or file dependency.
  2. Import GlitchPlayer and player/styles.css.
  3. Fetch the current presentation from GnommoEditors backend.
  4. Build a host-owned slide registry matching the templates GnommoEditor supports.
  5. Pass vote commits into a GnommoEditor API endpoint.
  6. Optionally use onEvent to power preview analytics or editor debugging.

That gives GnommoEditor responsibility for:

  • data
  • registry
  • persistence
  • product shell

And leaves GlitchPlayer responsible for:

  • playback
  • timeline
  • responsive stage layout
  • slide rendering
  • drag browsing
  • voting interaction UI

13. Suggested Next Steps Before Production Embedding

Before using this as a stable shared dependency, I recommend:

  1. Rename remaining internal lecture* event/type fields to presentation* where possible.
  2. Add a package export map for src/player/index.ts.
  3. Add a small integration example app or Storybook-like preview host.
  4. Add a few host-level visual regression tests for portrait and landscape framing.
  5. Decide whether the player package or the host app owns built-in default slide templates.

14. Current Reality Check

Right now, GlitchPlayer is reusable enough to embed in another React + Vite app, especially in a workspace-style setup.

It is not yet a polished published library, but it already has the right architectural boundary:

  • host app provides presentation + registry + persistence
  • GlitchPlayer provides runtime playback and interaction

That is the correct seam for moving this into GnommoEditor.