npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cygtut

v1.0.4

Published

JSON-driven tutorial overlay engine for web applications

Readme

CygTut

CygTut is a high-level tutorial engine built on top of cyghtml 0.1.2.

It owns tutorial meaning, runtime state, target lookup, anchors, grouping, command flow, and compilation. It does not own low-level DOM rendering. Final overlay DOM/CSS is rendered by cyghtml.

Status

This README describes the target CygTut specification.

The runtime is currently moving toward this shape. Some existing source files still reflect the earlier command surface, but this document is the reference for where the public authoring model should go next.

Philosophy

  1. CygTut is a tutorial engine first.
  2. CygTut should reuse as much of cyghtml as possible.
  3. The host page should stay untouched.
  4. Tutorial UI should render above the host page as overlay DOM.
  5. Target-based placement is the primary positioning model.
  6. Final rendering should always compile into ordinary HTML/CSS.
  7. State is persistent by default.
  8. Step-like progression should be expressed as named execution flow, not hidden screen replacement.
  9. Removal and reset should be explicit commands.
  10. Escape hatches for custom HTML/CSS should exist.

Relationship To cyghtml

The intended architecture is:

cygtut authoring script
-> cygtut runtime state
-> cygtut compile step
-> final cyghtml document
-> cyghtml renders overlay DOM

CygTut owns:

  • tutorial runtime state
  • tutorial variables
  • commands
  • trigger lifecycle
  • target selection
  • target anchor and self anchor resolution
  • object presets and tutorial object meaning
  • grouping rules
  • final position and size calculation
  • device-aware tutorial branching
  • conversion into a final cyghtml document

cyghtml owns:

  • overlay root mounting
  • final top-level vars, css, and html rendering
  • DOM creation from tag or markup
  • attrs, style, text, and items
  • parent-child composition
  • delayed parent resolution
  • runtime event hookup
  • node patching and final DOM commit

Core Runtime Model

The most important concept in CygTut is the overlay root.

The host page already exists. CygTut does not rewrite it. Instead, it creates overlay objects above that page.

Those overlay objects are placed like this:

  1. select a target element on the host page
  2. read the target's current rectangle
  3. choose a target anchor and self anchor
  4. compute final absolute overlay coordinates
  5. compile the result to cyghtml
  6. let cyghtml render the overlay DOM

Authoring Model

Top-Level Shape

{
  "meta": {},
  "vars": {},
  "boot": [],
  "flow": {}
}

Top-level keys:

  • meta: runtime and authoring settings
  • vars: initial variables
  • boot: execution nodes that run at startup
  • flow: named execution blocks

Why flow

CygTut no longer needs separate first-class steps and scenes as core language concepts. Both are just named execution blocks.

This means authors may use names like:

  • intro
  • login
  • step-1
  • done

All of them live under flow.

meta and active flow state

meta should not implicitly execute a flow block. It should only define startup bindings and initial runtime state.

Recommended direction:

{
  "meta": {
    "bindings": {
      "activeStage": {
        "path": "global.flow",
        "initial": "intro"
      }
    }
  }
}

Meaning:

  • activeStage is a friendly authoring name
  • path tells CygTut which runtime variable it binds to
  • initial sets the initial value of that variable at startup
  • this does not automatically execute flow.intro

If authors want the initial named flow block to run, that should happen explicitly through boot or through a trigger.

Example using boot:

{
  "meta": {
    "bindings": {
      "activeStage": {
        "path": "global.flow",
        "initial": "intro"
      }
    }
  },
  "boot": [
    {
      "type": "RUN",
      "path": "flow.intro"
    }
  ]
}

Example using a trigger command:

{
  "boot": [
    {
      "type": "ADD_TRIGGER",
      "id": "activate-intro",
      "watch": ["global.flow"],
      "if": "global.flow === 'intro'",
      "commands": [
        { "type": "RUN", "path": "flow.intro" },
        { "type": "REMOVE_TRIGGER", "id": "activate-intro" }
      ]
    }
  ]
}

This keeps startup state and flow execution separate, which makes the model easier to reason about.

Execution Nodes

CygTut uses two execution node shapes.

1. Action Node

An action node runs one command.

{
  "type": "SET_VAR",
  "key": "global.step",
  "value": 2,
  "if": "global.ready === true"
}

Fields:

  • type: required
  • if: optional condition
  • all other keys depend on the command type

2. Block Node

A block node groups commands and may gate them behind one condition.

{
  "if": "global.ready === true",
  "commands": [
    {
      "type": "SET_VAR",
      "key": "global.step",
      "value": 2
    }
  ]
}

Fields:

  • commands: required
  • if: optional condition

Common Rules

  • if means ��execute only if this expression is truthy��.
  • if a block node if is false, all child commands are skipped.
  • if an action node if is false, only that command is skipped.
  • block nodes may contain action nodes and other block nodes.
  • nesting is allowed.

Conditions

CygTut conditions use string expressions.

Recommended operators:

  • &&
  • ||
  • !
  • ===
  • !==
  • >
  • >=
  • <
  • <=
  • parentheses ()

Example:

{
  "if": "global.ready === true && global.device === 'mobile'"
}

Flow Execution

Named flow blocks are executed through RUN.

{
  "type": "RUN",
  "path": "flow.login"
}

Meaning:

  • resolve the named flow block
  • execute its nodes in order

Trigger Lifecycle As Commands

Triggers are not special top-level magic. They are runtime objects that may be added, updated, and removed through commands.

ADD_TRIGGER

{
  "type": "ADD_TRIGGER",
  "id": "login-ready",
  "watch": ["global.ready"],
  "if": "global.ready === true",
  "commands": [
    {
      "type": "RUN",
      "path": "flow.after-login"
    }
  ]
}

UPDATE_TRIGGER

{
  "type": "UPDATE_TRIGGER",
  "id": "login-ready",
  "patch": {
    "if": "global.ready === true && global.user !== null"
  }
}

REMOVE_TRIGGER

{
  "type": "REMOVE_TRIGGER",
  "id": "login-ready"
}

Trigger fields:

  • id: unique trigger id
  • watch: watched variable paths
  • if: optional condition
  • commands: execution nodes to run when the trigger fires

Command Reference

Object Commands

ADD_OBJECT

{
  "type": "ADD_OBJECT",
  "object": { ... }
}

Adds one tutorial object to runtime state.

UPDATE_OBJECT

{
  "type": "UPDATE_OBJECT",
  "id": "tip-1",
  "patch": { ... }
}

Updates part of an existing object.

REMOVE_OBJECT

{
  "type": "REMOVE_OBJECT",
  "id": "tip-1"
}

Removes one object.

Group Commands

CREATE_GROUP

{
  "type": "CREATE_GROUP",
  "object": {
    "id": "group-1",
    "type": "group"
  }
}

ADD_TO_GROUP

{
  "type": "ADD_TO_GROUP",
  "id": "tip-text",
  "groupId": "group-1"
}

REMOVE_FROM_GROUP

{
  "type": "REMOVE_FROM_GROUP",
  "id": "tip-text"
}

UNGROUP

{
  "type": "UNGROUP",
  "id": "group-1"
}

Variable Commands

SET_VAR

{
  "type": "SET_VAR",
  "key": "global.step",
  "value": 2
}

INC_VAR

{
  "type": "INC_VAR",
  "key": "global.count",
  "value": 1
}

TOGGLE_VAR

{
  "type": "TOGGLE_VAR",
  "key": "global.open"
}

Flow Commands

RUN

{
  "type": "RUN",
  "path": "flow.next"
}

EMIT

{
  "type": "EMIT",
  "name": "openModal",
  "payload": {
    "id": "login"
  }
}

Purpose:

  • bridge tutorial runtime to host application callbacks

Clear Commands

CLEAR_ALL

{
  "type": "CLEAR_ALL"
}

CLEAR_GROUP

{
  "type": "CLEAR_GROUP",
  "id": "group-1"
}

Why WAIT_UNTIL Is Not Core

The preferred model is:

  • immediate conditional execution through if
  • delayed state reaction through trigger commands

Because of that, WAIT_UNTIL is not part of the core spec. If it ever returns, it should be treated as optional authoring sugar rather than a central control-flow primitive.

Persistent Runtime Model

CygTut is persistent by default.

That means:

  • objects stay alive unless explicitly removed
  • moving to another named flow block does not automatically clear old objects
  • updates should modify existing objects when ids stay stable
  • hard reset should happen only through explicit clear or remove commands

This makes it possible to:

  • keep a tooltip alive across progression
  • update only text or style in the next flow block
  • move or restyle an object without recreating everything

Tutorial Object Model

Every tutorial object starts from a common shape.

{
  "id": "object-id",
  "type": "tooltip",
  "target": ".login-button",
  "targetAnchor": "bottom",
  "selfAnchor": "top",
  "offset": { "x": 0, "y": 12 },
  "autoPlacement": true,
  "x": "0px",
  "y": "0px",
  "width": "280px",
  "height": "120px",
  "minWidth": "200px",
  "minHeight": "80px",
  "maxWidth": "400px",
  "maxHeight": "240px",
  "className": "custom-class",
  "attrs": {},
  "style": {},
  "vars": {},
  "events": {},
  "triggers": [],
  "animation": {},
  "groupId": "group-1"
}

Common Fields

Required:

  • id
  • type

Placement:

  • target
  • targetAnchor
  • selfAnchor
  • offset
  • autoPlacement
  • x
  • y

Size:

  • width
  • height
  • minWidth
  • minHeight
  • maxWidth
  • maxHeight

Presentation:

  • className
  • attrs
  • style

Behavior:

  • vars
  • events
  • triggers
  • animation

Grouping:

  • groupId

Length Units

The following fields should support multiple length styles:

  • x
  • y
  • width
  • height
  • minWidth
  • minHeight
  • maxWidth
  • maxHeight

Allowed forms:

  • number
  • "120px"
  • "50%"
  • "auto"

Future-safe optional forms:

  • vw
  • vh
  • rem
  • em

Example:

{
  "x": "50%",
  "y": "24px",
  "width": "320px",
  "height": "auto"
}

Positioning Rules

Target-Based Placement

{
  "target": ".login-button",
  "targetAnchor": "bottom",
  "selfAnchor": "top",
  "offset": { "x": 0, "y": 12 },
  "autoPlacement": true
}

Rules:

  • target means target-based placement is active
  • target-following is always on for target-based objects
  • autoPlacement is optional and only adds fallback placement behavior

Absolute Placement

{
  "x": "120px",
  "y": "80px"
}

Rules:

  • if target is absent, direct coordinates are used

Object Types

tooltip

{
  "id": "tip-1",
  "type": "tooltip",
  "title": "Login Guide",
  "text": "Click the login button to continue."
}

highlight

{
  "id": "hl-1",
  "type": "highlight",
  "target": ".login-button",
  "padding": 8
}

text

{
  "id": "text-1",
  "type": "text",
  "text": "Hello"
}

button

{
  "id": "btn-1",
  "type": "button",
  "text": "Next"
}

mask

{
  "id": "mask-1",
  "type": "mask"
}

group

{
  "id": "group-1",
  "type": "group"
}

image

{
  "id": "img-1",
  "type": "image",
  "src": "/hero.png",
  "alt": "Hero"
}

progress

{
  "id": "progress-1",
  "type": "progress",
  "value": 2,
  "max": 5
}

custom

custom is the escape hatch for author-authored HTML.

{
  "id": "custom-1",
  "type": "custom",
  "markup": "<div class='panel'><strong>Hello</strong><button>Next</button></div>",
  "style": {
    "left": "120px",
    "top": "80px",
    "position": "absolute"
  }
}

Rules:

  • custom should accept markup
  • markup should contain exactly one root element
  • common object fields such as target, style, attrs, events, animation, and groupId still apply
  • built-in types remain semantic presets, while custom is render-oriented

Events

Object events are command arrays.

{
  "events": {
    "onClick": [
      {
        "type": "SET_VAR",
        "key": "global.step",
        "value": 2
      }
    ]
  }
}

Rules:

  • keys use DOM-style names like onClick, onMouseEnter, onMouseLeave
  • values are execution node arrays
  • final DOM event hookup is handled by cyghtml

Object-Local Trigger Rules

Objects may still carry local trigger definitions.

{
  "id": "next-btn",
  "type": "button",
  "vars": {
    "clicks": 0
  },
  "events": {
    "onClick": [
      { "type": "INC_VAR", "key": "object.clicks", "value": 1 }
    ]
  },
  "triggers": [
    {
      "watch": ["object.clicks"],
      "if": "object.clicks >= 1",
      "commands": [
        { "type": "SET_VAR", "key": "global.step", "value": 2 }
      ]
    }
  ]
}

Animation Model

Animation is object-level and CSS-oriented.

Recommended shape:

{
  "animation": {
    "enter": {
      "preset": "fade-in"
    },
    "loop": {
      "custom": {
        "animation": "pulse 1.2s ease-in-out infinite"
      }
    }
  }
}

Phases

  • enter
  • loop

Preset examples

  • fade-in
  • slide-up
  • slide-down
  • slide-left
  • slide-right
  • scale-in
  • pulse
  • rotate

Custom examples

  • animation
  • transition
  • transform
  • opacity

The compiler should resolve presets into final CSS values and emit any needed support styles into the final cyghtml document.

Patch Shape

UPDATE_OBJECT.patch is partial.

{
  "text": "New text",
  "title": "New title",
  "width": "320px",
  "style": {
    "background": "#111827"
  },
  "animation": {
    "enter": { "preset": "fade-in" }
  }
}

Rules:

  • patch only changes provided fields
  • unspecified fields remain unchanged
  • changing id or type through a patch is discouraged

Trigger Behavior

Triggers are still state-driven reactions.

Normal trigger model:

  1. a watched variable changes
  2. matching triggers are re-evaluated
  3. commands run
  4. tutorial object state changes
  5. a new cyghtml document is compiled
  6. the overlay is patched or rerendered

Target Lifecycle

Target handling is central to CygTut.

Current intended behavior:

  • if a target exists, compute geometry from its current getBoundingClientRect()
  • target-following is always on for target-based objects
  • if autoPlacement is enabled, try fallback anchor combinations when the first placement would leave the viewport
  • if a target does not exist yet, skip rendering that object for the current pass
  • if a target appears later, the next render pass may resolve it
  • if the host layout changes, recompute geometry and render again

Current runtime direction:

  • rerender on resize
  • rerender on scroll
  • observe known target elements with ResizeObserver when available
  • observe host-page DOM changes with MutationObserver when available
  • schedule repeated render requests to avoid excessive recompilation

Final Render Shape

The compiled result should be a plain cyghtml document.

{
  "vars": {
    "--tip-left": "120px",
    "--tip-top": "240px"
  },
  "css": {
    ".cygtut-tooltip": {
      "position": "absolute",
      "left": "var(--tip-left)",
      "top": "var(--tip-top)",
      "width": "280px",
      "padding": "16px",
      "borderRadius": "14px",
      "background": "#111827",
      "color": "#ffffff"
    }
  },
  "html": [
    {
      "tag": "div",
      "attrs": {
        "id": "login-tooltip",
        "class": "cygtut-tooltip"
      },
      "appendTo": "root",
      "items": [
        {
          "tag": "h3",
          "text": "Login Guide"
        },
        {
          "tag": "p",
          "text": "Click the login button to continue."
        }
      ]
    }
  ]
}

Processing Pipeline

authoring script
-> normalize
-> runtime state
-> commands and trigger lifecycle
-> target and anchor resolution
-> compile to cyghtml document
-> cyghtml patches or rerenders DOM

Public Runtime API

The public runtime shape is:

const tut = new CygTut({ script })

Current runtime surface should expose:

  • start()
  • destroy()
  • mount(target?)
  • mountById(id)
  • mountByClass(className)
  • mountByTag(tagName)
  • unmount()
  • run(path)
  • getVar(path)
  • setVar(path, value)
  • updateVars(record)
  • getSnapshot()
  • getRendererDocument()
  • subscribe(listener)

Recommended Usage

Vanilla

import { CygTut } from "cygtut";

const tut = new CygTut({ script });

tut.mountById("app");
await tut.start();
tut.setVar("global.step", 2);
console.log(tut.getVar("global.step"));
tut.destroy();

React

import { useRef } from "react";
import { CygTut, type CygTutHandle } from "cygtut/react";

export default function App() {
  const tutRef = useRef<CygTutHandle>(null);

  return (
    <>
      <CygTut ref={tutRef} script={script} />
      <button onClick={() => tutRef.current?.setVar("global.step", 2)}>
        next
      </button>
    </>
  );
}

Summary

CygTut should be a tutorial engine above cyghtml.

It should:

  • keep tutorial meaning and runtime state
  • resolve target-based placement
  • compile groups into real parent nodes
  • express final animation as CSS
  • compile the final result into a cyghtml document
  • let cyghtml render that document

Low-level overlay rendering belongs in cyghtml. High-level tutorial meaning belongs in cygtut.