DevTools
DevTools is a static utility class for editor hosts — a live preview, an inspector, a stage editor, a thumbnail renderer. It exposes the few engine internals such a host needs and that the ordinary story API deliberately does not: action identity, direct registration of a displayable, the current dialog line, and a layered image's per-layer sources.
You never need it to write a game.
import {DevTools} from "narraleaf-react/built-in";Every member is static — there is nothing to construct. DevTools is also re-exported from the package root (narraleaf-react), but the narraleaf-react/built-in entry is the one to prefer.
Action Identity
Every action in a story receives an id when the story is constructed: a generated one (a-0, a-1, ...) unless a static id was assigned to it, in which case that id is used instead. A static id is what makes an action addressable across rebuilds of the story — for example as the target of fastForward. Duplicate static ids are rejected at story construction.
getActionId
Read the resolved id of an action.
action: LogicAction.Actions- The action- Returns
string- The id assigned at story construction
const id = DevTools.getActionId(action);setActionId
Overwrite the resolved id of an action.
action: LogicAction.Actions- The actionid: string- The new id- Returns
LogicAction.Actions- The same action
DevTools.setActionId(action, "chapter1:intro");getStaticId
Read the static id of an action, or null when it has none and therefore takes a generated one.
action: LogicAction.Actions- The action- Returns
string | null
const staticId = DevTools.getStaticId(action);setStaticId
Assign the static id an action keeps across story construction. Pass null to drop it and fall back to a generated id.
action: LogicAction.Actions- The actionid: string | null- The static id- Returns
LogicAction.Actions- The same action
DevTools.setStaticId(action, "chapter1:intro");Chains and Actions
chainToActions
Unwrap a chainable expression into the flat list of actions it produced.
chain: Proxied<LogicAction.GameElement, Chained<LogicAction.Actions>>- A chained expression, see ChainedActions- Returns
LogicAction.Actions[]
const actions = DevTools.chainToActions(
image.char(["sad"]).darken(0.5, 300)
);wrapAction
Wrap actions — or a chain — into a single Control action, so a whole sequence can be handled as one statement.
action: LogicAction.Actions[] | Proxied<LogicAction.GameElement, Chained<LogicAction.Actions>>- The actions or chain to wrap- Returns
ControlAction
const wrapped = DevTools.wrapAction(image.char(["sad"]).darken(0.5, 300));Stage Introspection
getCurrentScene
getLayerSrcs
The src of each of a layered image's layers, bottom to top. A null entry is a layer that draws nothing for the given tags. A non-layered image yields an empty array.
A layered image has no single src to read — it is a stack — so a host rendering its own thumbnail of an on-stage element has to composite these itself, in order.
image: Image- See Imagetags?: string[]- The tags to resolve against. Defaults to the image's current tags.- Returns
(string | null)[]
// what the image is showing right now
const srcs = DevTools.getLayerSrcs(yuko);
// => ["yuko/body.png", "yuko/casual.png", "yuko/jacket.png", null, "yuko/mouth_sad.png"]
// what it would show for another set of tags, without touching the stage
const preview = DevTools.getLayerSrcs(yuko, ["uniform", "happy"]);
for (const src of preview) {
if (src === null) continue; // this layer draws nothing
// draw src onto your own canvas, in order
}getDisplayableTransformProps
Read a displayable's current transform-state props — position, opacity, zoom, rotation, scale, effects. Returns a shallow copy, so mutating it changes nothing on stage.
Intended for capturing a live pose, for example to prefill a motion editor with an element's current stage state.
displayable: LogicAction.DisplayableElements- See Displayable- Returns
Record<string, unknown>
const pose = DevTools.getDisplayableTransformProps(image);setDisplayableTransformProps
Overwrite a displayable's transform-state props with no animation, and push the result to the DOM immediately when the element is mounted.
By default the given props are merged over the current state; with merge: false the previous state is discarded entirely.
gameState: GameState- See GameStatedisplayable: LogicAction.DisplayableElements- The element to updateprops: Record<string, unknown>- The transform props to writeoptions?: { merge?: boolean }-mergedefaults totrue
DevTools.setDisplayableTransformProps(gameState, image, {opacity: 0.5});
// replace the whole state instead of merging into it
DevTools.setDisplayableTransformProps(gameState, image, pose, {merge: false});Throws if the transform state is locked by an in-flight transform. Only inject while the stage is idle.
registerDisplayable
Register a displayable into a scene's render tree without emitting a displayable:init action. The element renders immediately at its constructor-config transform state; visibility is governed by opacity > 0.
Intended for hosts that pre-pose a stage: construct elements with their computed state as constructor config — config state survives element.reset() and newGame() — then register them from a Script action or after mount.
The call is idempotent: an element the scene root's auto-init actions already registered is left alone.
gameState: GameState- See GameStatedisplayable: LogicAction.DisplayableElements- The element to registerscene?: Scene | null- Defaults tonull, see Scenelayer?: Layer | null- Defaults tonull, see Layer
const ghost = new Image({src: "yuko/body.png", opacity: 0.4});
DevTools.setElementId(ghost, "preview:ghost");
DevTools.registerDisplayable(gameState, ghost, scene, layer);setElementId
Assign an explicit element id.
Elements reachable from a scene's action tree receive generated ids (e-0, e-1, ...) at story construction. An element registered directly via registerDisplayable is outside that tree and would otherwise keep the default id, colliding with others as a React key — so give it a unique id, with a distinct prefix so it never collides with a generated one.
element: LogicAction.GameElement- The elementid: string- The id to assign
DevTools.setElementId(ghost, "preview:ghost");Dialog
getCurrentDialog
Read the dialog line currently presented to the player, or null when no dialog is on screen. Covers both ADV and NVL presentation.
gameState: GameState- See GameState- Returns
DevToolsCurrentDialog | null- See DevToolsCurrentDialog
const dialog = DevTools.getCurrentDialog(gameState);
if (dialog?.ended) {
console.log("waiting for the player to advance", dialog.actionId);
}onDialogStateChange
Subscribe to changes of the currently presented dialog line — creation, typing completion, advance and settle — across both ADV and NVL modes.
The listener carries no payload; call getCurrentDialog to read the new state.
gameState: GameState- See GameStatelistener: () => void- Called on every change- Returns
LiveGameEventToken- See LiveGameEventToken
const token = DevTools.onDialogStateChange(gameState, () => {
const dialog = DevTools.getCurrentDialog(gameState);
highlightRow(dialog?.actionId ?? null);
});
return function cleanup() {
token.cancel();
};DevToolsCurrentDialog
Snapshot of the dialog line currently presented to the player.
type DevToolsCurrentDialog = {
/** Id of the say action that produced the line (static id when assigned). */
actionId: string | null;
/** True once the line finished displaying and awaits advance. */
ended: boolean;
mode: "adv" | "nvl";
};Persistent
getNamespaceName
The name a Persistent is registered under in the store — the same string Storable.getNamespace takes.
persistent: Persistent<any>- See Persistent- Returns
string
const name = DevTools.getNamespaceName(playerPersistent);DynamicPersistent
The DynamicPersistent class, which is otherwise not exported from the package. It is the internal Persistent variant whose namespace is prefixed and created at runtime — a scene's locals are one.
const Dynamic = DevTools.DynamicPersistent;Internal by design. It is reachable here for hosts that have to mirror the engine's own namespaces, not for story code.