@velo/ui (0.1.0)

Published 2026-10-15 19:04:05 +02:00 by r59q

Installation

@velo:registry=
npm install @velo/ui@0.1.0
"@velo/ui": "0.1.0"

About this package

@velo/ui

Game-neutral React command tiles, tooltips and the Debug Tools model-preview screen shared by Crownfall and Riftwake. Games supply their own model catalogues, preview factories and inspector content; the package never imports either game.

Install

The package is public on the Forgejo npm registry owned by velo. Add the scope to the consuming project's .npmrc:

@velo:registry=https://git.amhk.dk/api/packages/velo/npm/

React, ReactDOM and Babylon are peer dependencies, so the game owns exactly one copy of each. Declare them in the game itself:

npm install @velo/ui react@^19.3.0 react-dom@^19.3.0 @babylonjs/core@^9.29.0
npm install --save-dev @types/react@^19.3.0 @types/react-dom@^19.3.0

Every automatic release is a new 0.x.0 minor and ^0.N.0 accepts only 0.N.x, so adopting a release is a deliberate range and lockfile bump in the game.

Imports

Everything is exported from the package root:

import {
  CommandTile,
  CommandTooltipProvider,
  DebugTools,
  DebugModelPreview,
  PreviewAnimationControls,
  filterDebugModels,
  createInspectionPreview,
  framePreviewModel,
  PreviewPlayback,
  type CommandTileProps,
  type DebugToolsProps,
  type DebugModel,
  type ModelPreview,
  type PreviewModel,
  type PreviewModelFactory,
  type PreviewAnimation,
  type PreviewPlaybackState,
} from '@velo/ui';

The built JavaScript is plain ES modules with .js specifiers, including Babylon deep imports such as @babylonjs/core/scene.js, so it loads in Node (for unit tests) as well as in bundlers. JSX consumers compile with "jsx": "react-jsx" under either NodeNext or Bundler resolution. Babylon's declarations reference WebGPU types, so consumers use skipLibCheck (as both games do) or install @webgpu/types.

Stylesheet

The Debug Tools styles are a separate file and are not imported by the JavaScript. Load @velo/ui/debug-tools.css where you mount DebugTools, for example next to its lazy import:

if (new URL(location.href).searchParams.get('debug') === 'tools') {
  const { DebugTools } = await import('@velo/ui');
  await import('@velo/ui/debug-tools.css');
  root.render(<DebugTools gameName="Game" gamePath={import.meta.env.BASE_URL} {...loaders} />);
}

CommandTile uses the class names command-tile, tile-button, tile-badge and tile-tooltip (with data-open) and ships no styles; each game styles its own palettes.

Lifecycle obligations

  • Tooltips. Wrap every palette that should share one open tooltip in a single CommandTooltipProvider; a tile outside a provider never opens its tooltip. Each tooltip is a portal on document.body with the given tooltipId, so ids must be unique in the document. An open tooltip listens for window resize and capture-phase scroll to stay inside the viewport and removes those listeners when it closes or unmounts. A tile whose palette is collapsed (no layout) closes its tooltip.
  • Debug Tools. loadModels returns the game's catalogue and loadPreviewModel returns its PreviewModelFactory; both may lazily import game code. Keep both functions stable (module-level or memoised), because a new identity reloads the catalogue or preview. Game-specific inspectors are passed as children. The screen never reads or writes session storage. Return to {game} links to gamePath without the debug parameter; when the screen is a popup opened from that game page on the same origin, the link focuses the opener and closes the popup instead of navigating, so a popup that inherited the player's session never starts a second session.
  • Model preview. createInspectionPreview(canvas, modelId, factory, onError, onPlaybackChange) owns one Babylon Engine, Scene, camera, light, resize observer, context-menu listener and PreviewPlayback. Call dispose() exactly when the preview is no longer shown; it is safe during loading and idempotent. The factory must create everything inside the supplied scene, so scene disposal releases it; it must not touch gameplay state. DebugModelPreview does this for React.
  • Playback. PreviewPlayback samples clips on an inspection clock in seconds, never the gameplay clock. Loops reload the clip so procedural state restarts. After dispose() it ignores every call.
  • Framing. framePreviewModel fits enabled, visible meshes under the root and throws a readable error for empty or degenerate geometry.

Browser checks for the canvas, camera and data attributes (data-camera-*, data-animation-time) live in each game's debug-tools browser spec; the shared scenario helper stays in the repository tests/ and drives the screen through its public DOM only.

Local development

From this directory:

npm ci
npm run typecheck      # sources and tests
npm run lint
npm test               # playback, framing, tooltip and Debug Tools contracts (happy-dom)
npm run build          # dist/ with JavaScript, declarations, source maps and debug-tools.css
npm run test:consumer  # pack, install into a clean project, type-check JSX and load JS + CSS

test:consumer installs the tarball with only the declared peers and type packages, fails on any out-of-range peer or second React/ReactDOM/Babylon copy, and rejects built imports of CSS, undeclared modules or repository source. Pass -- --keep to keep the temporary project.

Optional local linking

To try unpublished changes in a game that already depends on @velo/ui, build here and install the directory into the game without saving it:

npm run build
cd ../../games/<game> && npm install --no-save ../../packages/ui

Until each game migrates, the games still import this package's src/ directly. Their Vite configs dedupe React and Babylon for that, but TypeScript and tsx resolve those imports from packages/ui/node_modules when it exists, which yields duplicate Babylon/React types in root checks. Remove packages/ui/node_modules before running root type-checks or game unit tests.

Publishing

The Publish UI workflow (.forgejo/workflows/publish-ui.yml) runs on pushes to main that change this directory and on manual dispatch. It runs the checks and the packed-consumer check before it configures the registry token, then picks the next 0.x.0 above the registry's versions, builds and publishes to https://git.amhk.dk/api/packages/velo/npm/. The committed version is not the release version. The PACKAGE_REGISTRY_TOKEN Actions secret must belong to an account with package write access.

Dependencies

Development dependencies

ID Version
@babylonjs/core ^9.29.0
@happy-dom/global-registrator ^21.0.1
@types/node ^26.6.4
@types/react ^19.3.0
@types/react-dom ^19.3.0
eslint ^10.12.0
react ^19.3.0
react-dom ^19.3.0
tsx ^4.23.15
typescript ^5.9.3
typescript-eslint ^8.71.1

Peer dependencies

ID Version
@babylonjs/core ^9.29.0
react ^19.3.0
react-dom ^19.3.0
Details
npm
2026-10-15 19:04:05 +02:00
953
UNLICENSED
20 KiB
Assets (1)
ui-0.1.0.tgz 20 KiB
Versions (2) View all
0.2.0 2026-10-29
0.1.0 2026-10-15