How the Global Step Counter Works in the Presentation Skill

The global step counter flattens the hierarchical chapter-and-step structure of a presentation into a single linear index, enabling seamless navigation and progress tracking across the entire scripted talk.

In the ConardLi/garden-skills repository, the web-video-presentation skill manages scripted presentations as a collection of chapters, each containing ordered narration steps. To allow the UI to treat the entire presentation as a flat sequence—supporting features like progress bars and global seeking—the useStepper hook implements a global step counter that maps two-dimensional coordinates (chapter, step) to a single integer. This derivation enables consistent state persistence and navigation regardless of chapter boundaries.

Understanding the Hierarchical Step Structure

A presentation in this system consists of an array of ChapterDef objects (defined in skills/web-video-presentation/templates/src/registry/chapters.ts), where each chapter contains a narrations array. The internal state tracks the current position using a cursor object with chapter and step indices. However, for UI components like progress indicators or seekable timelines, exposing this as a single number simplifies rendering and interaction logic significantly.

Computing the Global Step Counter

The useStepper hook (located at skills/web-video-presentation/templates/src/hooks/useStepper.ts) derives the global counter through three coordinated calculations that execute during initialization and whenever the chapter configuration changes.

Calculating Chapter Offsets

First, the hook builds an offsets array that records the cumulative number of steps preceding each chapter. This allows O(1) lookup of a chapter's starting position in the global sequence.

const offsets = useMemo(() => {
  const arr: number[] = [];
  let acc = 0;
  for (const c of chapters) {
    arr.push(acc);
    acc += c.narrations.length;
  }
  return arr;
}, [chapters]);

As implemented in lines 74-81, offsets[i] represents the global index of the first step in chapter i. For example, if chapter 0 has 5 narrations and chapter 1 has 3, the offsets array becomes [0, 5, 8].

Determining the Total Step Count

The total number of steps across the entire presentation is computed via a simple reduction (lines 84-86):

const totalGlobal = useMemo(
  () => chapters.reduce((s, c) => s + c.narrations.length, 0),
  [chapters],
);

This totalGlobal value serves as the upper bound for the global index and drives percentage-based calculations in progress indicators.

Deriving the Current Global Index

With the offsets established, the hook calculates the current global index by adding the chapter's offset to the intra-chapter step index (line 87):

const globalIndex = (offsets[cursor.chapter] ?? 0) + cursor.step;

This value is exposed as part of the StepperState return object, allowing consumers to display "Step X of Y" labels or calculate completion percentages without understanding the underlying chapter structure.

The counter enables bidirectional navigation through the jumpToGlobal function (lines 123-136). This method accepts a global index, clamps it to valid bounds, then traverses the chapters to locate the corresponding (chapter, step) pair:

const jumpToGlobal = useCallback(
  (g: number) => {
    const target = clamp(g, 0, totalGlobal - 1);
    let acc = 0;
    for (let i = 0; i < chapters.length; i++) {
      const t = chapters[i]!.narrations.length;
      if (target < acc + t) {
        setCursor({ chapter: i, step: target - acc });
        return;
      }
      acc += t;
    }
  },
  [chapters, totalGlobal],
);

This linear search efficiently maps the flat index back to the hierarchical cursor format. When the cursor updates, the hook persists the state to localStorage and recomputes globalIndex, ensuring the UI remains synchronized across browser refreshes.

Practical Usage Examples

Components consuming the useStepper hook can leverage globalIndex and totalGlobal to build intuitive navigation controls.

Implementing a Seekable Progress Bar

The following component renders a clickable progress bar that jumps to the corresponding global step when clicked:

import { useStepper } from "./hooks/useStepper";

function ProgressBar({ chapters }) {
  const {
    globalIndex,
    totalGlobal,
    jumpToGlobal,
  } = useStepper(chapters);

  const percent = (globalIndex / Math.max(1, totalGlobal)) * 100;

  return (
    <div style={{ width: "100%", background: "#eee" }}>
      <div
        style={{
          width: `${percent}%`,
          background: "#4caf50",
          height: "8px",
        }}
        onClick={e => {
          const rect = (e.target as HTMLElement).getBoundingClientRect();
          const clickPos = (e.clientX - rect.left) / rect.width;
          jumpToGlobal(Math.floor(clickPos * totalGlobal));
        }}
      />
    </div>
  );
}

Displaying Global Step Labels

For simple progress indication, consume the global values directly:

function StepLabel({ chapters }) {
  const { globalIndex, totalGlobal } = useStepper(chapters);
  return <p>Step {globalIndex + 1} of {totalGlobal}</p>;
}

Both patterns rely on the flattened coordinate system provided by the useStepper hook, abstracting away the complexity of multi-chapter boundary calculations.

Summary

  • The global step counter transforms the two-dimensional (chapter, step) structure into a single linear index for simplified UI state management.
  • The offsets array caches cumulative step counts per chapter, enabling O(1) global index calculation via offsets[cursor.chapter] + cursor.step.
  • The totalGlobal value aggregates all narration steps across chapters, supporting percentage-based progress calculations.
  • The jumpToGlobal function provides reverse mapping from a flat index back to the hierarchical cursor, enabling seekable navigation controls.
  • State synchronization with localStorage ensures the global position persists across page reloads, maintaining user context within the presentation flow.

Frequently Asked Questions

What is the purpose of the global step counter in the presentation skill?

The global step counter serves as a flattened coordinate system that allows UI components to treat the entire multi-chapter presentation as a single linear sequence. This abstraction simplifies the implementation of progress bars, step counters, and seek functionality by eliminating the need to handle chapter boundaries manually.

How does jumpToGlobal handle out-of-bounds indices?

The function clamps the requested index between 0 and totalGlobal - 1 using a clamp utility before processing. This ensures that navigation requests—whether from user interactions like clicking a progress bar or programmatic calls—always resolve to valid presentation steps without throwing errors.

Where is the current step state persisted?

The useStepper hook synchronizes the cursor state (containing chapter and step indices) to localStorage whenever it changes. Because the global index is derived from the cursor rather than stored independently, the system maintains consistency between the hierarchical state and the flattened representation across browser sessions.

Why use an offsets array instead of calculating positions on demand?

Pre-computing the offsets array with useMemo optimizes performance for presentations with many chapters. Since the global index calculation offsets[cursor.chapter] + cursor.step occurs frequently—whenever the cursor changes or components re-render—the O(1) array lookup prevents repeated iteration through preceding chapters that would occur with on-demand calculation.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →