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

cyghtml

v0.1.2

Published

Low-level JSON-driven overlay HTML renderer for Cygnus libraries.

Readme

cyghtml

cyghtml is a low-level JSON-driven overlay HTML renderer.

Its purpose is simple:

  • create a top-most overlay root above an existing web page
  • render HTML elements into that overlay from JSON
  • apply CSS and CSS variables from JSON
  • keep the runtime close to normal HTML and CSS semantics
  • default to a non-blocking overlay root, while allowing an interactive root when the overlay itself should behave like an app UI

cyghtml is not a tutorial engine. It does not own tutorial steps, scenes, tooltip presets, highlight presets, or tutorial progression logic. Those belong in a higher-level library such as cygtut.

Philosophy

cyghtml follows these design rules.

  1. It is a renderer first.
  2. It should stay close to HTML and CSS.
  3. It should default to a non-blocking overlay root for page overlays, but allow an interactive overlay root when needed.
  4. It should avoid inventing new UI concepts unless absolutely necessary.
  5. It should accept final render data, not tutorial meaning.
  6. A higher-level library may compute positions, state, and tutorial rules, then pass the result into cyghtml.
  7. cyghtml should focus on overlay mounting, DOM creation, CSS application, parent-child composition, and final rendering.

In short:

  • cygtut decides what to show
  • cyghtml shows it

Relationship To cygtut

The intended architecture is:

cygtut -> builds final overlay JSON -> cyghtml -> renders overlay DOM

That means:

  • cygtut can calculate tutorial state, step changes, anchors, and presets
  • cyghtml receives the final JSON tree and renders it
  • cyghtml should remain reusable outside tutorials

Top-Level Keys

A cyghtml document is built around three top-level keys.

  • vars
  • css
  • html

Example:

{
  "vars": {
    "--panel-x": "120px",
    "--panel-y": "80px",
    "--panel-bg": "#111827",
    "--panel-color": "#ffffff"
  },
  "css": {
    ".panel": {
      "position": "absolute",
      "left": "var(--panel-x)",
      "top": "var(--panel-y)",
      "background": "var(--panel-bg)",
      "color": "var(--panel-color)",
      "padding": "16px",
      "border-radius": "14px"
    }
  },
  "html": [
    {
      "tag": "div",
      "attrs": {
        "id": "panel",
        "class": "panel"
      },
      "appendTo": "root",
      "items": [
        {
          "tag": "h3",
          "text": "Login Guide"
        }
      ]
    }
  ]
}

Top-Level vars

vars defines runtime variables.

In the first version, the most natural use is CSS variables, but the runtime may also keep these values available for later expansion.

Example:

{
  "vars": {
    "--panel-x": "120px",
    "--panel-y": "80px",
    "--panel-width": "280px",
    "--panel-bg": "#111827"
  }
}

Recommended usage:

  • CSS custom properties such as --panel-x
  • values that higher-level systems want to inject before render

A higher-level library such as cygtut may rebuild the final JSON whenever its own state changes. cyghtml itself does not need to become a full tutorial state engine.

Top-Level css

css defines CSS rules.

The simplest model is a JSON object whose keys are CSS selectors and whose values are CSS declarations.

Example:

{
  "css": {
    ":root": {
      "--panel-bg": "#111827"
    },
    ".panel": {
      "position": "absolute",
      "left": "120px",
      "top": "80px",
      "width": "280px",
      "padding": "16px",
      "background": "var(--panel-bg)",
      "color": "#ffffff"
    },
    ".panel h3": {
      "margin": "0 0 8px 0"
    }
  }
}

What css Can Contain

Inside css:

  • selector keys such as .panel, #panel, :root, .panel button
  • declaration objects whose keys are CSS properties
  • values written as normal CSS strings

Examples of valid declaration keys:

  • position
  • left
  • top
  • width
  • height
  • padding
  • margin
  • background
  • background-color
  • border-radius
  • font-size
  • z-index
  • animation
  • transform

The goal is to stay as close to normal CSS as possible.

Top-Level html

html defines the DOM that should be rendered into the overlay.

html is an array of node definitions.

Example:

{
  "html": [
    {
      "tag": "div",
      "attrs": {
        "id": "panel",
        "class": "panel"
      },
      "appendTo": "root",
      "items": [
        {
          "tag": "h3",
          "text": "Login Guide"
        },
        {
          "tag": "button",
          "text": "Next"
        }
      ]
    }
  ]
}

html Node Model

Each entry inside top-level html is a node definition.

A node may use either:

  • tag
  • or markup

Only one should be used for a single node.

If both are used, the result becomes ambiguous and should be treated as invalid authoring.

Common Node Keys

A node may contain:

  • tag
  • markup
  • attrs
  • style
  • appendTo
  • appendToClass
  • hostAppendTo
  • hostAppendToClass
  • items
  • text
  • events

These keys are explained below.

tag

tag creates a normal HTML element.

Example:

{
  "tag": "div"
}

What tag Can Contain

tag is a string such as:

  • div
  • span
  • button
  • p
  • h1
  • h2
  • h3
  • img
  • section
  • article
  • any valid HTML tag you want to support

tag Example

{
  "tag": "button",
  "attrs": {
    "id": "next",
    "type": "button"
  },
  "text": "Next"
}

markup

markup lets a node be created from a raw HTML string.

Example:

{
  "markup": "<div id='panel' class='panel'></div>"
}

Rules For markup

  • it should produce exactly one outer root element
  • inline HTML attributes inside the markup itself are valid, including things such as class, id, data-*, and style="..."
  • attrs, style, appendTo, appendToClass, hostAppendTo, hostAppendToClass, and items apply to that outer root element
  • when both inline markup styles and outer style are present, the runtime may apply outer style last so it behaves like a final patch on the root element
  • it is useful when you want direct HTML syntax instead of tag plus attrs

markup Example

{
  "markup": "<div id='panel' class='panel'></div>",
  "style": {
    "left": "120px",
    "top": "80px",
    "position": "absolute"
  },
  "appendTo": "root",
  "items": [
    {
      "tag": "h3",
      "text": "Login Guide"
    }
  ]
}

attrs

attrs defines standard HTML attributes.

Example:

{
  "attrs": {
    "id": "panel",
    "class": "panel",
    "data-mode": "intro"
  }
}

What attrs Can Contain

Any normal HTML attribute is valid, for example:

  • id
  • class
  • type
  • src
  • href
  • title
  • alt
  • role
  • tabindex
  • data-*
  • aria-*

If a node uses appendTo, the parent lookup should use normal HTML-style ids. In other words, appendTo: "panel" refers to the element whose attrs.id is panel.

style

style defines inline CSS for that node.

Example:

{
  "style": {
    "position": "absolute",
    "left": "120px",
    "top": "80px",
    "width": "280px",
    "background": "#111827"
  }
}

What style Can Contain

Any normal inline-style-like CSS property may be used, for example:

  • position
  • left
  • top
  • right
  • bottom
  • width
  • height
  • padding
  • margin
  • background
  • backgroundColor
  • color
  • display
  • flexDirection
  • gap
  • borderRadius
  • transform
  • animation
  • transition
  • zIndex

The runtime may normalize camelCase and kebab-case if desired, but the intent is that style behaves like inline CSS.

appendTo

appendTo defines the parent node by HTML id inside the cyghtml-managed overlay tree.

Example:

{
  "appendTo": "panel"
}

What appendTo Can Contain

appendTo may contain:

  • root
  • the id of another cyghtml-created node

Examples:

  • appendTo: "root"
  • appendTo: "panel"
  • appendTo: "footer"

appendToClass

appendToClass defines the parent node by CSS class inside the cyghtml-managed overlay tree.

Example:

{
  "appendToClass": "panel"
}

What appendToClass Can Contain

appendToClass may contain:

  • a class name without the leading .

Examples:

  • appendToClass: "panel"
  • appendToClass: "footer"

appendToClass Parent Rule

When appendToClass is used:

  • the runtime should find the first matching rendered overlay element with that class
  • that first matched overlay element becomes the parent

hostAppendTo

hostAppendTo defines the parent node by HTML id on the host page outside the cyghtml-managed tree.

Example:

{
  "hostAppendTo": "app-shell"
}

What hostAppendTo Can Contain

hostAppendTo may contain:

  • the id of an element already present on the host page

hostAppendToClass

hostAppendToClass defines the parent node by CSS class on the host page outside the cyghtml-managed tree.

Example:

{
  "hostAppendToClass": "app-shell"
}

What hostAppendToClass Can Contain

hostAppendToClass may contain:

  • a class name without the leading .

hostAppendToClass Parent Rule

When hostAppendToClass is used:

  • the runtime should find the first matching host-page element with that class
  • that first matched host-page element becomes the parent

Parent Resolution Rule

Parent lookup should follow this order:

  1. appendTo
  2. appendToClass
  3. hostAppendTo
  4. hostAppendToClass

If none of them are present:

  • for a top-level node in html, the runtime may treat root as the default parent
  • for a node inside items, the current containing node becomes the default parent

This makes nested authoring simpler.

items

items is a list of child node definitions.

Important rule:

  • every entry inside items uses the same node schema as a top-level html node
  • that means an item may also have its own tag, markup, attrs, style, appendTo, appendToClass, hostAppendTo, hostAppendToClass, items, text, or events

Example:

{
  "tag": "div",
  "attrs": {
    "id": "panel"
  },
  "items": [
    {
      "tag": "div",
      "attrs": {
        "id": "header",
        "class": "panel-header"
      },
      "items": [
        {
          "tag": "h3",
          "text": "Login Guide"
        }
      ]
    }
  ]
}

Parent Rule For items

If an item does not specify appendTo, its default parent is the current outer node.

So in this example:

{
  "tag": "div",
  "attrs": {
    "id": "panel"
  },
  "items": [
    {
      "tag": "h3",
      "text": "Login Guide"
    }
  ]
}

The h3 is automatically appended to panel.

If an item does specify appendTo, that explicit value wins.

text

text defines plain text content for a node.

Example:

{
  "tag": "button",
  "text": "Next"
}

text Rules

  • text behaves like textContent
  • it works whether the node is top-level or inside items
  • it is most natural with tag
  • if markup is used, text should usually not be used at the same time because the intent becomes unclear

events

events defines DOM event bindings for a node.

Example:

{
  "tag": "button",
  "attrs": {
    "id": "next"
  },
  "text": "Next",
  "events": {
    "click": "nextHandler"
  }
}

What events Can Contain

events is an object whose keys are DOM event names and whose values point to runtime handlers.

Examples of event keys:

  • click
  • mouseenter
  • mouseleave
  • input
  • change
  • focus
  • blur

How events Should Work

A handler name in JSON should be resolved against a runtime handler registry.

Example:

const cygh = new CygHtmlEngine()
cygh.handlers.nextHandler = () => {
  console.log("next")
}

If JSON contains:

{
  "events": {
    "click": "nextHandler"
  }
}

then the runtime should attach the DOM event so that clicking the element calls:

cygh.handlers["nextHandler"]

Design Rule For events

For true JSON compatibility, the preferred form is a handler name string.

That means this is the recommended model:

{
  "events": {
    "click": "nextHandler"
  }
}

instead of embedding raw JavaScript functions inside JSON.

Variable Usage

Top-level vars values should be usable throughout the document.

Important rule:

  • variable references should be resolved first, before normal render processing continues

That means the runtime should first build the vars map, then replace variable references in supported string values, and only then continue with node creation and final DOM commit.

The simplest first rule is:

  • vars values may be used in css
  • vars values may be used in inline style
  • vars values may be used in markup
  • vars values may also be compared in runtime conditions if cyghtml later exposes a condition system

Variable Usage In css

The most natural use in CSS is standard CSS variable syntax.

Example:

{
  "vars": {
    "--panel-x": "120px"
  },
  "css": {
    ".panel": {
      "left": "var(--panel-x)"
    }
  }
}

Variable Usage In style

Inline style should follow the same rule.

Example:

{
  "style": {
    "left": "var(--panel-x)",
    "top": "var(--panel-y)"
  }
}

Variable Usage In markup

Because markup is raw HTML, variables may appear there as normal CSS variable usage inside inline styles or other supported string positions.

Example:

{
  "markup": "<div id='panel' style='left:var(--panel-x); top:var(--panel-y);'></div>"
}

Design rule:

  • the runtime should resolve these variable references before final markup handling continues
  • in practice, cyghtml should process variable substitution first, then parse and patch the resulting markup

Variable Usage In Plain Values

If cyghtml later needs to support value interpolation outside CSS, the cleanest direction is explicit placeholder syntax.

Example:

{
  "text": "Current mode: {{mode}}"
}

But for the initial version, CSS variable usage is the clearest and most natural model.

Variable Comparison

If cyghtml ever exposes conditional rendering or conditional patching later, variable comparison should use explicit condition expressions.

Examples:

  • mode === "intro"
  • count > 3
  • open === true
  • device === "mobile" || device === "tablet"

That comparison system should remain optional. For the initial renderer-focused version of cyghtml, variables mainly exist to support rendering and styling.

Full Example

{
  "vars": {
    "--panel-x": "120px",
    "--panel-y": "80px",
    "--panel-bg": "#111827",
    "--panel-color": "#ffffff"
  },
  "css": {
    ":root": {
      "--shadow": "0 12px 30px rgba(0,0,0,0.24)"
    },
    ".panel": {
      "position": "absolute",
      "left": "var(--panel-x)",
      "top": "var(--panel-y)",
      "background": "var(--panel-bg)",
      "color": "var(--panel-color)",
      "padding": "16px",
      "border-radius": "14px",
      "box-shadow": "var(--shadow)",
      "width": "280px"
    },
    ".panel button": {
      "margin-top": "12px"
    }
  },
  "html": [
    {
      "tag": "div",
      "attrs": {
        "id": "panel",
        "class": "panel"
      },
      "appendTo": "root",
      "items": [
        {
          "tag": "h3",
          "attrs": {
            "id": "title"
          },
          "text": "Login Guide"
        },
        {
          "tag": "p",
          "attrs": {
            "id": "desc"
          },
          "text": "This overlay is rendered by cyghtml."
        },
        {
          "tag": "button",
          "attrs": {
            "id": "next",
            "type": "button"
          },
          "text": "Next"
        }
      ]
    }
  ]
}

Processing Pipeline

This section describes how a cyghtml JSON document should move through the runtime, which files are responsible for each stage, and in what order the work happens.

The goal is to make the runtime behavior explicit before implementation begins.

End-To-End Flow

At a high level, the runtime flow is:

user JSON
-> CygHtmlEngine
-> OverlayRoot
-> CssRuntime
-> HtmlRuntime
-> pending parent resolution
-> final DOM commit to overlay root

A higher-level library such as cygtut may build the final JSON first, but once the JSON reaches cyghtml, the processing model should stay the same.

Input JSON Example

{
  "vars": {
    "--panel-x": "120px",
    "--panel-y": "80px"
  },
  "css": {
    ".panel": {
      "position": "absolute",
      "left": "var(--panel-x)",
      "top": "var(--panel-y)"
    }
  },
  "html": [
    {
      "tag": "div",
      "attrs": {
        "id": "panel",
        "class": "panel"
      },
      "appendTo": "root",
      "items": [
        {
          "tag": "h3",
          "text": "Login Guide"
        }
      ]
    }
  ]
}

Step 1: JSON Enters CygHtmlEngine

File:

  • src/engine/CygHtmlEngine.ts

The engine is the main coordinator.

When code like this runs:

cygh.render(doc)

or:

cygh.json = doc
cygh.render()

The engine should:

  1. store the incoming JSON as the current document
  2. store or merge vars into runtime variable storage if needed
  3. reset temporary render state for the new pass
  4. coordinate the rest of the pipeline

At this stage, the JSON is still only data. Nothing has been attached to the DOM yet.

Step 2: Create Or Reuse The Overlay Root

File:

  • src/renderer/OverlayRoot.ts

The overlay root is the top-most container created above the existing web page.

Responsibilities here:

  1. mount above document.body by default when no explicit host is chosen
  2. create the root if it does not already exist
  3. reuse it if it already exists
  4. prepare a safe rendering container for cyghtml nodes

Conceptually, the page becomes:

<body>
  ...existing page...
  <div data-cyghtml-root="true"></div>
</body>

At this point, cyghtml has a valid rendering destination, but no user nodes are attached yet.

Step 3: Resolve Top-Level vars

Files:

  • src/engine/CygHtmlEngine.ts
  • src/renderer/CssRuntime.ts
  • src/renderer/HtmlRuntime.ts

The runtime should resolve top-level vars values before normal CSS and HTML processing continues.

This is important because a variable may appear inside:

  • css
  • inline style
  • markup
  • text-like string values
  • parent-related string values if the system later allows that

So the correct model is:

  1. read top-level vars
  2. build a runtime variable map
  3. replace variable references in all supported JSON string fields first
  4. only after that continue with CSS generation, node creation, style application, and parent linking

Example:

{
  "vars": {
    "--panel-x": "120px",
    "--panel-bg": "#111827"
  }
}

If a later field contains values such as:

{
  "style": {
    "left": "var(--panel-x)"
  },
  "markup": "<div style='background:var(--panel-bg); left:var(--panel-x);'></div>"
}

then those variable expressions should be resolved before the renderer applies style or parses the markup for final DOM creation.

This matters because early variable resolution makes later stages predictable. For example:

  • storing processed node state becomes simpler
  • markup parsing becomes more stable
  • style patching becomes more stable
  • parent linking or delayed node attachment can work with already-resolved values

Step 4: Apply Top-Level css

File:

  • src/renderer/CssRuntime.ts

The runtime then processes the top-level css section.

Responsibilities:

  1. read selector keys
  2. read declaration objects
  3. generate CSS text or CSSOM rules
  4. attach the generated rules to the runtime style container

Example input:

{
  "css": {
    ".panel": {
      "position": "absolute",
      "left": "var(--panel-x)",
      "top": "var(--panel-y)"
    }
  }
}

Important note:

The runtime may keep CSS application scoped to its own overlay output so host-page styles and overlay styles remain easier to reason about.

Step 5: Read Top-Level html

Files:

  • src/engine/CygHtmlEngine.ts
  • src/renderer/HtmlRuntime.ts

The engine now walks the top-level html array.

Each item in the array is treated as a node definition. The engine should pass each node definition into the HTML runtime.

At this stage, the runtime should not assume that parent nodes already exist. This is important because parent references may be forward-declared.

Step 6: Create DOM Nodes From tag Or markup

File:

  • src/renderer/HtmlRuntime.ts

For each HTML node definition, the runtime should create a real DOM node.

There are two main paths.

Path A: tag

If the node uses tag:

  1. call the DOM equivalent of document.createElement(tag)
  2. create exactly one real element node
  3. keep a temporary registry entry for that node

Example:

{
  "tag": "button",
  "attrs": {
    "id": "next"
  },
  "text": "Next"
}

Path B: markup

If the node uses markup:

  1. parse the raw markup string
  2. require exactly one outer root element
  3. take that outer root as the node's real DOM element
  4. keep a temporary registry entry for that node

Example:

{
  "markup": "<div id='panel' class='panel'></div>"
}

At the end of this step, the runtime has created real DOM elements, but they may not yet be attached to their final parents.

Step 7: Apply Node-Level attrs

File:

  • src/renderer/HtmlRuntime.ts

Once a node exists, attrs should be applied.

Responsibilities:

  1. set standard HTML attributes
  2. preserve authoring ids and classes exactly as written unless the runtime explicitly documents another transform
  3. keep normal HTML semantics predictable

Example input:

{
  "attrs": {
    "id": "panel",
    "class": "panel active",
    "data-mode": "intro"
  }
}

The expected result is that those attributes are applied directly to the rendered node.

Step 8: Apply Node-Level style

File:

  • src/renderer/HtmlRuntime.ts

After attributes, the runtime should apply inline style.

Responsibilities:

  1. map JSON style keys to DOM style assignments
  2. allow normal CSS-like values
  3. if the node came from markup, apply style to the outer root element
  4. if markup already contains inline style, apply the outer JSON style last as a final patch

Example:

{
  "style": {
    "position": "absolute",
    "left": "120px",
    "top": "80px"
  }
}

Step 9: Apply text

File:

  • src/renderer/HtmlRuntime.ts

If a node defines text, the runtime should apply it like textContent.

Example:

{
  "tag": "h3",
  "text": "Login Guide"
}

If a node uses markup, text should usually be avoided because the meaning becomes ambiguous. That should be treated as an authoring rule.

Step 10: Walk items

Files:

  • src/engine/CygHtmlEngine.ts
  • src/renderer/HtmlRuntime.ts

If a node contains items, each item is processed using the exact same node schema.

That means each item may itself contain:

  • tag
  • markup
  • attrs
  • style
  • appendTo
  • appendToClass
  • hostAppendTo
  • hostAppendToClass
  • items
  • text
  • events

Processing rule:

  1. create the child node
  2. if no explicit parent is set, use the current containing node as the default parent
  3. if an explicit parent is set, store that relation for later resolution

Step 11: Store Parent Links Instead Of Attaching Immediately

File:

  • src/engine/CygHtmlEngine.ts

This is one of the most important parts of the design.

cyghtml should not require perfect authoring order. That means the engine should be allowed to create nodes first and connect them later.

So for every node, the engine should store:

  • the real DOM element
  • the authoring id if present
  • the desired parent target
  • whether that parent target is by id, by class, or by root

A simple internal registry may look like:

node registry:
- panel -> HTMLElement
- title -> HTMLElement
- next -> HTMLElement

pending parent links:
- next -> panel
- title -> panel

This allows forward references such as:

{
  "html": [
    {
      "tag": "button",
      "attrs": {
        "id": "next"
      },
      "appendTo": "panel",
      "text": "Next"
    },
    {
      "tag": "div",
      "attrs": {
        "id": "panel"
      },
      "appendTo": "root"
    }
  ]
}

Even though panel appears later, the final connection can still succeed.

Step 12: Resolve appendTo

File:

  • src/engine/CygHtmlEngine.ts

When resolving parent links:

  1. if appendTo is root, attach the node to the overlay root
  2. if appendTo is an id, find the registered node with that authoring id
  3. attach the child to that resolved parent

appendTo should take priority over appendToClass.

Step 13: Resolve appendToClass

Files:

  • src/engine/CygHtmlEngine.ts
  • src/renderer/HtmlRuntime.ts

If appendTo is missing and appendToClass is present:

  1. find the first matching class target in the runtime-managed rendered set
  2. use that first matched element as the parent
  3. attach the child to it

This rule should stay simple and explicit. The first match wins.

Step 14: Final DOM Commit

Files:

  • src/engine/CygHtmlEngine.ts
  • src/renderer/HtmlRuntime.ts

Once all nodes are created and all parent links are resolved, the runtime commits the result into the overlay root.

That means:

  1. top-level root-attached nodes are appended to the overlay root
  2. child nodes are appended to their resolved parents
  3. the final overlay DOM becomes visible above the page

At this point, the current JSON document has fully become real overlay DOM.

Step 15: Re-Render On New JSON

File:

  • src/engine/CygHtmlEngine.ts

When the outside system provides a new JSON document:

  1. the engine stores it as the new current document
  2. previous runtime render state is cleared or replaced
  3. the pipeline runs again from the beginning

This means cyghtml can stay simple:

  • it stores the current JSON
  • when JSON changes, it renders again

A higher-level library such as cygtut may decide when and why the JSON changes. cyghtml only needs to render the new result.

File-By-File Responsibility In The Pipeline

src/engine/CygHtmlEngine.ts

Responsible for:

  • storing current JSON
  • storing runtime vars
  • starting render passes
  • building and keeping the node registry
  • keeping unresolved parent links
  • resolving parent links in the correct order
  • clearing and destroying runtime state

src/renderer/OverlayRoot.ts

Responsible for:

  • creating the top-most overlay container
  • mounting to body by default
  • exposing the root element back to the engine
  • cleaning up the overlay root when needed

src/renderer/CssRuntime.ts

Responsible for:

  • applying top-level vars
  • applying top-level css
  • generating runtime stylesheets
  • attaching generated runtime CSS in a predictable overlay-owned way

src/renderer/HtmlRuntime.ts

Responsible for:

  • creating nodes from tag
  • creating nodes from markup
  • applying attrs
  • applying style
  • applying text
  • returning finished DOM nodes back to the engine

src/types/schema.ts

Responsible for:

  • defining the JSON schema
  • defining top-level vars, css, and html
  • defining the node shape and allowed keys

src/index.ts

Responsible for:

  • exporting CygHtmlEngine
  • exporting public document and node types

Why This Pipeline Matters

This processing order makes cyghtml predictable.

It allows:

  • normal HTML/CSS-like authoring
  • forward parent references
  • parent resolution at the end
  • markup or tag based authoring
  • clean separation from higher-level libraries such as cygtut

The important design boundary is:

  • cyghtml turns final JSON into overlay DOM
  • cygtut decides what JSON should exist in the first place

Runtime Class

The main runtime class is planned as:

const cygh = new CygHtmlEngine(options?)

cyghtml is a library package, so its runtime API should be documented clearly.

Runtime Properties

A CygHtmlEngine instance should expose these public properties.

json

Current cyghtml document stored on the engine.

Example:

cygh.json = doc
cygh.render()

Purpose:

  • stores the current source document
  • lets the engine re-render from the current document without requiring the caller to pass it every time

vars

Runtime variable storage.

Example:

cygh.vars.abc = 1
console.log(cygh.vars.abc)

Purpose:

  • keep runtime values that outside systems want to read or write
  • provide a simple shared value bag for the current render runtime

root

Reference to the actual overlay root element created by the engine.

Example:

console.log(cygh.root)

Purpose:

  • gives direct access to the mounted overlay root
  • is read-oriented runtime state, not the preferred way to choose the mount target
  • useful for debugging or advanced integrations

interactive

Whether the overlay root should allow pointer interaction at the root level.

Example:

const cygh = new CygHtmlEngine({ interactive: true })
console.log(cygh.interactive)
``

Purpose:

- `false` keeps the overlay root non-blocking by default
- `true` allows the overlay root itself to behave like an interactive app container
- useful for app-like demos, dashboards, or full overlay UIs

### `mounted`

Boolean-like mounted state.

Example:

```ts
console.log(cygh.mounted)

Purpose:

  • tells whether the overlay root is currently attached and active

handlers

Runtime handler registry.

Example:

cygh.handlers.nextHandler = () => {
  console.log("next")
}

Purpose:

  • stores named runtime functions for events bindings
  • lets JSON stay data-oriented while runtime code provides actual functions

Runtime Methods

A CygHtmlEngine instance should expose these public methods.

render(document?)

Render a full cyghtml document. If a document is passed, store it first as the current json and then render it. If not, render the currently stored json.

cygh.render(doc)
cygh.render()

Purpose:

  • store the current source document when provided
  • apply top-level vars
  • apply top-level css
  • build the overlay DOM from top-level html

destroy()

Destroy the overlay runtime.

cygh.destroy()

Purpose:

  • remove the overlay root
  • clear style tags and rendered nodes
  • release internal references

clear()

Clear rendered overlay content without destroying the instance itself.

cygh.clear()

Purpose:

  • empty current overlay nodes
  • keep the engine instance reusable

setVar(name, value)

Write a runtime variable.

cygh.setVar("abc", 1)

Purpose:

  • explicit variable write API
  • preferred over assigning random properties directly on the engine instance

getVar(name)

Read a runtime variable.

console.log(cygh.getVar("abc"))

Purpose:

  • explicit variable read API

setVars(record)

Write many runtime variables at once.

cygh.setVars({ abc: 1, mode: "intro" })

Purpose:

  • convenient multi-variable update API

getSnapshot()

Return a runtime snapshot.

console.log(cygh.getSnapshot())

A snapshot may contain:

  • current vars
  • the last rendered document
  • root or mount state
  • internal node registry state if the public API chooses to expose it

Constructor options

Supported options:

  • document?: Document
  • interactive?: boolean

Example:

const passive = new CygHtmlEngine()
const interactive = new CygHtmlEngine({ interactive: true })
``

Default behavior:

- `interactive` defaults to `false`
- when `interactive` is `false`, the root uses `pointer-events: none`
- when `interactive` is `true`, the root uses `pointer-events: auto`

### `mount(target?)`

Mount the overlay root.

```ts
cygh.mount()
cygh.mount(document.body)

Purpose:

  • create and attach the overlay root when needed
  • use document.body as the default host when no target is provided
  • support custom host mounting in advanced scenarios

mountById(id)

Mount the overlay root to the first host element found by id.

cygh.mountById("app")

mountByClass(className)

Mount the overlay root to the first host element found by class name.

cygh.mountByClass("app-shell")

mountByTag(tagName)

Mount the overlay root to the first host element found by tag name.

cygh.mountByTag("main")

unmount()

Detach the overlay root without fully destroying the engine instance.

cygh.unmount()

Purpose:

  • temporarily remove the overlay from the page
  • preserve engine state if desired

removeNode(id)

Remove a rendered node by authoring id.

cygh.removeNode("panel")

Purpose:

  • remove a specific rendered node
  • remove the matching stored JSON subtree when available
  • keep future render() calls from restoring the removed node unless new JSON is provided

updateNode(id, patch)

Partially update one existing authoring node.

cygh.updateNode("panel", {
  style: { left: "240px" }
})

Purpose:

  • mutate one stored JSON node
  • trigger a stable re-render from the updated state

replaceNode(id, node)

Replace one existing authoring node.

cygh.replaceNode("title", {
  tag: "h2",
  attrs: { id: "title" },
  text: "Updated title"
})

appendNode(parentId, node)

Append a child node to a managed parent or to root.

cygh.appendNode("panel", {
  tag: "button",
  attrs: { id: "next", type: "button" },
  text: "Next"
})

prependNode(parentId, node)

Insert a child node at the beginning of a managed parent or root.

cygh.prependNode("root", {
  tag: "div",
  attrs: { id: "banner" },
  text: "Pinned first"
})

Recommended Public Usage

The normal usage style should look like this:

const cygh = new CygHtmlEngine()

cygh.handlers.nextHandler = () => {
  console.log("next")
}

cygh.setVar("abc", 1)
console.log(cygh.getVar("abc"))

cygh.json = {
  vars: {
    "--panel-x": "120px"
  },
  css: {
    ".panel": {
      "position": "absolute",
      "left": "var(--panel-x)"
    }
  },
  html: [
    {
      "tag": "div",
      "attrs": {
        "id": "panel",
        "class": "panel"
      },
      "appendTo": "root",
      "text": "Hello"
    }
  ]
}

cygh.render()

Render Modes

cyghtml should support two runtime usage styles.

1. Full render

This is the current default model.

cygh.render(doc)

or:

cygh.json = doc
cygh.render()

Meaning:

  • store the current JSON document
  • resolve vars
  • clear current overlay children
  • rebuild CSS and DOM from the stored document

This is the safest and simplest mode. It is the best fit for higher-level runtimes such as cygtut, where the final overlay document may be fully recompiled whenever tutorial state changes.

2. Incremental update

This is the current node-level update model.

It should allow users to keep the current rendered tree and update only part of it instead of always doing a full rebuild.

Examples:

cygh.updateNode("panel", {
  style: { left: "240px" }
})

cygh.updateNode("title", {
  text: "Updated title"
})

cygh.replaceNode("title", {
  tag: "h2",
  attrs: { id: "title" },
  text: "Updated title"
})

cygh.appendNode("panel", {
  tag: "button",
  attrs: { id: "next" },
  text: "Next"
})

Current goals:

  • keep the existing overlay DOM when only a small part changes
  • update both the live DOM and the stored json
  • let later render() calls continue from the updated stored state
  • fall back to subtree replacement when a fine-grained patch is too complex

Why both modes matter

Both models are useful.

Full render is better when:

  • a higher-level engine recompiles the whole overlay document
  • layout or structure changes broadly
  • correctness matters more than patch efficiency

Incremental update is better when:

  • a text label changes
  • a class or style needs to be toggled
  • one node is appended or removed
  • a small subtree changes without affecting the rest of the overlay

Incremental Update API

The current runtime already supports:

  • render(document?)
  • updateNode(id, patch)
  • replaceNode(id, node)
  • appendNode(parentId, node)
  • prependNode(parentId, node)
  • removeNode(id)
  • clear()
  • destroy()

The current runtime now supports node-level update methods by mutating the stored json and then re-running render() for a stable rebuild.

updateNode(id, patch)

Partially update one existing authoring node and then re-render from the updated stored json.

Planned patch surface:

  • attrs
  • style
  • text
  • markup
  • events
  • items

Important rule:

  • this updates the stored JSON node first
  • the current runtime then calls render() so the live DOM is rebuilt from the updated state

replaceNode(id, node)

Replace a rendered node with a new node definition, then re-render from the updated stored json.

This is useful when a node changes too much for a small patch to stay simple.

appendNode(parentId, node)

Append a new child node under an existing managed parent node, then re-render from the updated stored json.

prependNode(parentId, node)

Insert a new child node at the beginning of an existing managed parent node, then re-render from the updated stored json.

removeNode(id)

removeNode(id) now removes the matching stored JSON subtree when possible and then re-renders.

If the node exists only in the current live DOM and not in stored json, it falls back to removing the live DOM subtree directly.

Internal Rule For Incremental Updates

Because cyghtml now supports node-level updates, the runtime should not treat DOM and JSON as separate truths.

The rule should be:

  • stored json is the canonical render state
  • DOM is the live projection of that state
  • incremental updates must mutate stored json and then keep the DOM in sync through re-rendering or targeted subtree rebuilding

That means a future patch flow should look like this:

update request
-> locate node in stored json
-> patch stored json
-> patch live DOM or rebuild the affected subtree
-> keep the rest of the tree intact

This keeps full render and incremental update compatible with each other.

Design Rule For Runtime State

The engine instance itself should not be used like this:

cygh.abc = 1

Even though JavaScript technically allows arbitrary properties, cyghtml should treat runtime values as:

  • cygh.vars.abc = 1
  • cygh.setVar("abc", 1)
  • cygh.getVar("abc")

This keeps the public API explicit and easier to maintain.

Why This Model

This model keeps cyghtml close to normal web authoring.

  • attrs.id is a normal HTML id
  • appendTo works like a parent reference
  • tag behaves like document.createElement(tag)
  • markup behaves like raw HTML input
  • style behaves like inline CSS
  • css behaves like stylesheet rules
  • vars behaves like injected CSS variables or external values

The result is not a tutorial DSL. It is a low-level HTML/CSS overlay description format.

Folder Structure

The planned cyghtml project structure is:

cyghtml/
  README.md
  package.json
  src/
    engine/
      CygHtmlEngine.ts
    renderer/
      OverlayRoot.ts
      CssRuntime.ts
      HtmlRuntime.ts
    types/
      schema.ts
    index.ts

This is not just a recommendation. This is the intended implementation structure for cyghtml.

File Roles

src/engine/CygHtmlEngine.ts

Main runtime coordinator.

Responsibilities:

  • accept a full cyghtml document
  • keep a temporary node registry
  • resolve pending parent links
  • call renderer helpers
  • expose lifecycle methods such as render, removeNode, clear, and destroy

src/renderer/OverlayRoot.ts

Responsible for overlay mounting.

Responsibilities:

  • create and own the top-most overlay root
  • mount above document.body by default
  • clear or destroy the overlay when needed

src/renderer/CssRuntime.ts

Responsible for top-level CSS and variable application.

Responsibilities:

  • apply vars
  • generate style tags from css
  • keep selector rules attached to the overlay document

src/renderer/HtmlRuntime.ts

Responsible for turning node definitions into actual DOM nodes.

Responsibilities:

  • create nodes from tag
  • create nodes from markup
  • apply attrs
  • apply style
  • apply text
  • attach nodes to resolved parents

src/types/schema.ts

Type definitions for the cyghtml document.

Responsibilities:

  • define top-level vars, css, html
  • define node schema
  • define appendTo, appendToClass, hostAppendTo, hostAppendToClass, items, markup, tag, attrs, style, text, and events

src/index.ts

Public package entry.

Responsibilities:

  • export the engine
  • export public types

Detailed Usage Examples

This section shows how cyghtml is meant to be used in practice.

1. Basic Overlay Render

import { CygHtmlEngine } from "cyghtml";

const cygh = new CygHtmlEngine({ interactive: true });

cygh.render({
  vars: {
    "--panel-x": "120px",
    "--panel-y": "80px",
    "--panel-bg": "#111827"
  },
  css: {
    ".panel": {
      position: "absolute",
      left: "var(--panel-x)",
      top: "var(--panel-y)",
      width: "280px",
      padding: "16px",
      borderRadius: "14px",
      background: "var(--panel-bg)",
      color: "#ffffff"
    }
  },
  html: [
    {
      tag: "div",
      attrs: {
        id: "panel",
        class: "panel"
      },
      appendTo: "root",
      items: [
        {
          tag: "h3",
          text: "Hello from cyghtml"
        },
        {
          tag: "p",
          text: "This node is rendered into the overlay root."
        }
      ]
    }
  ]
});

What this does:

  • creates or reuses the overlay root
  • applies vars to the runtime
  • applies the stylesheet from css
  • creates the panel node
  • appends it to root
  • creates child items and appends them to panel

2. Rendering With Stored json

import { CygHtmlEngine } from "cyghtml";

const cygh = new CygHtmlEngine({ interactive: true });

cygh.json = {
  vars: {
    "--panel-x": "24px",
    "--panel-y": "24px"
  },
  css: {
    ".panel": {
      position: "absolute",
      left: "var(--panel-x)",
      top: "var(--panel-y)",
      padding: "12px",
      background: "#111827",
      color: "#ffffff"
    }
  },
  html: [
    {
      tag: "div",
      attrs: { id: "panel", class: "panel" },
      appendTo: "root",
      text: "Render using stored json"
    }
  ]
};

cygh.render();

This is useful when another runtime updates cygh.json first and only then asks cyghtml to render.

3. Mounting To A Custom Host

By default, cyghtml mounts above document.body.

By default, the root also uses pointer-events: none, which is ideal for guide overlays that should not block the host page. If the overlay itself is meant to be the interactive UI, use interactive: true. You can also choose another host.

const cygh = new CygHtmlEngine({ interactive: true });

cygh.mountById("app");

cygh.render({
  html: [
    {
      tag: "div",
      attrs: { id: "panel" },
      appendTo: "root",
      text: "Mounted inside #app instead of body"
    }
  ]
});

You can also use:

cygh.mountByClass("app-shell");
cygh.mountByTag("main");
cygh.mount(document.body);

4. Events And Handlers

import { CygHtmlEngine } from "cyghtml";

const cygh = new CygHtmlEngine({ interactive: true });

cygh.handlers.nextHandler = () => {
  console.log("next clicked");
};

cygh.render({
  html: [
    {
      tag: "button",
      attrs: {
        id: "next",
        type: "button"
      },
      style: {
        position: "absolute",
        left: "24px",
        top: "24px",
        pointerEvents: "auto"
      },
      appendTo: "root",
      text: "Next",
      events: {
        click: "nextHandler"
      }
    }
  ]
});

How this works:

  • the JSON keeps only the handler name
  • the runtime resolves that name from cygh.handlers
  • clicking the button calls cygh.handlers.nextHandler

5. Using markup With Outer Patches

const cygh = new CygHtmlEngine({ interactive: true });

cygh.render({
  html: [
    {
      markup: "<div id='panel' class='panel' style='padding:12px;'><strong>Markup root</strong></div>",
      style: {
        position: "absolute",
        left: "120px",
        top: "80px",
        background: "#111827",
        color: "#ffffff"
      },
      appendTo: "root",
      items: [
        {
          tag: "button",
          attrs: {
            id: "next",
            type: "button"
          },
          style: {
            marginTop: "12px",
            pointerEvents: "auto"
          },
          text: "Next"
        }
      ]
    }
  ]
});

Important rule:

  • markup must create exactly one outer root element
  • outer style, attrs, and items apply to that outer root element
  • inline markup styles are allowed
  • outer style acts like a final patch on the root element

6. Nested items

const cygh = new CygHtmlEngine({ interactive: true });

cygh.render({
  html: [
    {
      tag: "div",
      attrs: {
        id: "panel",
        class: "panel"
      },
      style: {
        position: "absolute",
        left: "120px",
        top: "80px",
        width: "280px"
      },
      appendTo: "root",
      items: [
        {
          tag: "div",
          attrs: {
            id: "header",
            class: "panel-header"
          },
          items: [
            {
              tag: "h3",
              text: "Nested header"
            }
          ]
        },
        {
          tag: "div",
          attrs: {
            id: "footer"
          },
          items: [
            {
              tag: "button",
              attrs: {
                id: "next",
                type: "button"
              },
              text: "Next"
            }
          ]
        }
      ]
    }
  ]
});

Default item rule:

  • if an item does not define a parent override, its parent becomes the current containing node
  • that means header and footer become children of panel
  • and the nested h3 becomes a child of header

7. Forward Parent References

cyghtml allows delayed parent resolution. That means authoring order does not have to be perfect.

const cygh = new CygHtmlEngine({ interactive: true });

cygh.render({
  html: [
    {
      tag: "button",
      attrs: {
        id: "next",
        type: "button"
      },
      appendTo: "panel",
      text: "Next"
    },
    {
      tag: "div",
      attrs: {
        id: "panel"
      },
      appendTo: "root"
    }
  ]
});

Even though panel appears later, the runtime can still:

  • create both nodes first
  • store parent links temporarily
  • resolve the final parent relationship at the end

8. Appending To Existing Host Page DOM

Sometimes a node should attach to an element that already exists on the page outside the cyghtml-managed overlay tree.

By host id

const cygh = new CygHtmlEngine({ interactive: true });

cygh.render({
  html: [
    {
      tag: "div",
      attrs: { id: "inline-panel" },
      hostAppendTo: "app-shell",
      text: "Attached to existing host node by id"
    }
  ]
});

By host class

const cygh = new CygHtmlEngine({ interactive: true });

cygh.render({
  html: [
    {
      tag: "div",
      attrs: { id: "inline-panel" },
      hostAppendToClass: "app-shell",
      text: "Attached to first existing host node by class"
    }
  ]
});

Host parent lookup rules:

  • hostAppendTo looks up an existing host-page element by id
  • hostAppendToClass looks up the first matching host-page class element
  • these host lookups should stay outside the cyghtml-managed overlay tree

9. Updating Variables And Re-Rendering

const cygh = new CygHtmlEngine({ interactive: true });

cygh.render({
  vars: {
    "--panel-x": "24px"
  },
  css: {
    ".panel": {
      position: "absolute",
      left: "var(--panel-x)",
      top: "24px"
    }
  },
  html: [
    {
      tag: "div",
      attrs: { id: "panel", class: "panel" },
      appendTo: "root",
      text: "Move me by changing vars"
    }
  ]
});

cygh.setVar("--panel-x", "200px");
cygh.render();

This works because:

  • the engine keeps json
  • the engine keeps runtime vars
  • render() resolves vars first
  • the next render re-applies CSS and DOM from the current state

10. Cleanup

const cygh = new CygHtmlEngine({ interactive: true });

cygh.render(doc);
cygh.removeNode("panel");
cygh.clear();
cygh.unmount();
cygh.destroy();

Use these when:

  • removeNode(id) removes one rendered subtree by authoring id
  • clear() removes current overlay children but keeps the instance alive
  • unmount() removes the overlay root from the page
  • destroy() fully tears down runtime state

Summary

cyghtml is a low-level overlay renderer.

It should:

  • accept vars, css, and html
  • stay close to HTML and CSS semantics
  • support both tag and markup
  • support nested items
  • allow delayed parent resolution through appendTo, appendToClass, hostAppendTo, and hostAppendToClass
  • render final DOM into a top-most overlay root

And it should deliberately stop there.

Higher-level meaning such as tutorial flow belongs in cygtut.