Skip to content

Navigation

These commands and selectors move the reader through the story.

Novel keeps track of where the reader is with a call stack: a list of LexiaRefs that records the reader’s position and how they got there.

Starts the novel from the beginning.

startNovel(): Promise<void>

Goes to the startup.startLabel from your project settings, and draws the opening scene.

Example
const { startNovel } = getCommands();
await startNovel();

Moves the reader to a new place in the story.

navigate(ctx?: NavigateContext): Promise<void>
interface NavigateContext {
label?: string;
lexiaRef?: LexiaRef;
link?: LinkLexia;
type?: "call" | "jump" | "return";
}
FieldDescription
labelGoes to a section or identity label.
lexiaRefGoes to that lexia.
linkFollows the link.
typeHow the call stack changes. See below.

With no destination, navigate moves on from the reader’s current position, following any silent links in the beat.

TypeCall stack
"jump"Replaces the top of the stack, so there’s no way back. This is the default.
"call"Adds to the stack, so the story can return to where it branched.
"return"Removes the top of the stack, and returns the reader to where the last call was made.

To keep some of your plugin’s state in step with the call stack, see withCallStack.

Example
const { navigate } = getCommands();
await navigate(); // advance
await navigate({ link }); // the reader chose this link
await navigate({ label: "the-docks" }); // go to a label
await navigate({ label: "flashback", type: "call" }); // call
await navigate({ type: "return" }); // come back

Cancels a navigation that failed partway through, and discards any uncommitted state.

abortNavigation(ctx: AbortNavigationContext): Promise<void>
interface AbortNavigationContext {
error: unknown;
}

navigate calls this when any stage of a beat throws an error. It discards state back to the last commit with discardState, then throws the error again.

Decorate it to handle errors more gently than a crash. Let next run so the state is still discarded, and catch the error it throws.

Example
decorateCommand("abortNavigation", async (context, next) => {
try {
await next(context);
} catch (error) {
showApology(error);
}
});

Changes the call stack without loading or drawing anything.

updateCallStack(ctx: UpdateCallStackContext): Promise<void>
interface UpdateCallStackContext {
lexiaRef?: LexiaRef;
type?: "call" | "jump" | "return";
}

A "jump" or "call" needs a lexiaRef. A "return" doesn’t take one, because its destination is already on the stack.

Example
const { updateCallStack } = getCommands();
await updateCallStack({ lexiaRef, type: "call" });
await updateCallStack({ type: "return" });

Moves the reader forward one step through their history, and redraws the scene.

stepForward(): Promise<void>

Wraps the SDK’s stepForward with a redraw.

Example
const { stepForward } = getCommands();
await stepForward();

Moves the reader back one step through their history, and redraws the scene.

stepBack(): Promise<void>

Wraps the SDK’s stepBack with a redraw.

Example
const { stepBack } = getCommands();
await stepBack();

Returns the LexiaRef a screenplay label points to.

resolveLabel(ctx: ResolveLabelContext): Promise<ResolveLabelResult>
interface ResolveLabelContext {
label: string;
}
interface ResolveLabelResult {
lexiaRef: LexiaRef;
}

Throws an error if the label isn’t in the label manifest.

Example
const { resolveLabel } = getCommands();
const { lexiaRef } = await resolveLabel({ label: "chapter-two" });

Returns the beat that comes after the current one.

resolveNextBeat(
ctx: ResolveNextBeatContext,
): Promise<null | ResolveNextBeatResult>
interface ResolveNextBeatContext {
lexiaRef: LexiaRef;
link?: LinkLexia;
}
interface ResolveNextBeatResult {
lexiaRef: LexiaRef;
type?: "call" | "jump";
}
  • If the beat has a silent link, it follows that link.
  • If the beat has any other links, it returns null, because the reader has to choose one.
  • Otherwise, it moves on to the next beat in the screenplay.
Example
const { resolveNextBeat } = getCommands();
const next = await resolveNextBeat({ lexiaRef });

Returns the index of the top of the call stack.

getCallStackIndex(ctx: GetCallStackContext): { index: number }

The SDK’s withCallStack mixin uses this selector, and throws if no plugin provides it.

Example
const { getCallStackIndex } = getSelectors();
const { index } = getCallStackIndex();