Demo Page Generator

SkillDev tools

Use when creating a demo or test page for manual testing of Handsontable. Trigger when the user asks to create a demo, test page, repro page, reproduction case, manual test, or wants to verify a bug fix or feature visually. Also trigger when the user mentions dev-generated.html, dev-pr.html, dev-latest.html, dev.html, or wants to compare behavior between a released version and a local build. Use this for any PR that needs a manual testing artifact.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Demo Page Generator skill

What this skill tells your AI

The instructions your AI receives, as published by handsontable/handsontable in .claude/skills/handsontable-demo-page/SKILL.md and read by ahel’s review.

Generate two self-contained HTML demo pages for manual testing:

FileLoads fromPurpose
handsontable/dev-latest.htmljsDelivr CDN (latest published version)Shows the current/buggy behavior
handsontable/dev-pr.htmlLocal dist/ (built from the branch)Shows the fix or new feature

Both files are gitignored (dev*.html pattern), so they never pollute the repo.

Each file links to the other at the top, so a reviewer can switch back and forth without losing scroll position or state context. Two separate files means complete JS/CSS isolation — no dual-instance loading tricks, no stylesheet toggling, no shared globals between versions.

Step 1 — Analyze the PR context

Before generating the demo, understand what needs testing. Read the relevant source changes to determine:

  • What bug is being fixed, or what feature is being added?
  • Which plugins, editors, cell types, or settings are involved?
  • What user interaction reproduces the issue (click, double-click, keyboard, scroll, touch)?
  • Are there specific data shapes needed (merged cells, nested headers, large datasets)?

Use git log and git diff against the base branch to understand the changes. If there's a linked GitHub issue, read it for reproduction steps.

Step 2 — Build Handsontable

Always run the build unconditionally — do not check whether dist/ exists first:

npm run build --prefix handsontable

Step 3 — Generate the two demo files

Write both files using the templates below. Both are gitignored (dev*.html), so they never pollute the repo.

Files already exist? Both files are throwaway — generated by a previous task. Skip reading them. Wipe them first, then write fresh:

rm -f handsontable/dev-pr.html handsontable/dev-latest.html

Then use the Write tool to create each file from scratch.

Each file is a standalone single-instance page. The nav bar at the top links to the other file — the current file's link is styled as active (non-clickable) so the reviewer always knows where they are.

Template — handsontable/dev-latest.html (Released / CDN)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>[short description] — Released v__RELEASED_VERSION__</title>
  <link rel="stylesheet"
    href="https://cdn.jsdelivr.net/npm/handsontable@__RELEASED_VERSION__/styles/ht-theme-main.min.css">
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; padding: 16px; background: #f5f5f5; }
    h1 { font-size: 18px; margin-bottom: 12px; }
    nav { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; }
    .nav-label { font-size: 13px; color: #666; margin-right: 4px; }
    .nav-btn {
      padding: 6px 16px; border-radius: 6px; font-size: 14px; text-decoration: none;
      border: 1px solid #ccc;
    }
    .nav-btn.active { background: #fff; font-weight: 600; border-color: #999; color: #111; pointer-events: none; }
    .nav-btn:not(.active) { background: #e9e9e9; color: #333; }
    .nav-btn:not(.active):hover { background: #f0f0f0; }
    .badge { display: inline-block; font-size: 11px; padding: 1px 7px; border-radius: 10px; margin-left: 6px; vertical-align: middle; }
    .badge-cdn { background: #e3f2fd; color: #1565c0; }
    .badge-local { background: #e8f5e9; color: #2e7d32; }
    .info { background: #fff3cd; border: 1px solid #ffc107; border-radius: 6px; padding: 12px 16px; margin-bottom: 16px; font-size: 14px; line-height: 1.5; }
    .info strong { color: #856404; }
  </style>
</head>
<body>
  <h1>[Short description of what is being tested]</h1>

  <nav>
    <span class="nav-label">Version:</span>
    <span class="nav-btn active">Released <span class="badge badge-cdn">v__RELEASED_VERSION__</span></span>
    <a class="nav-btn" href="dev-pr.html">PR Build <span class="badge badge-local">local</span></a>
  </nav>

  <div class="info">
    <strong>How to test:</strong> [Step-by-step reproduction instructions]
  </div>

  <div id="hot-container"></div>

  <script src="https://cdn.jsdelivr.net/npm/handsontable@__RELEASED_VERSION__/dist/handsontable.full.min.js"></script>
  <script>
    // window.hot is exposed for browser console / DevTools debugging
    window.hot = new Handsontable(document.getElementById('hot-container'), {
      // === ADAPT THIS CONFIG TO THE TEST CASE ===
      data: Handsontable.helper.createSpreadsheetData(10, 6),
      colHeaders: true,
      rowHeaders: true,
      width: '100%',
      height: 320,
      themeName: 'ht-theme-main',
      licenseKey: 'non-commercial-and-evaluation',
    });
  </script>
</body>
</html>

Template — handsontable/dev-pr.html (PR Build / local)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>[short description] — PR Build</title>
  <link rel="stylesheet" href="styles/ht-theme-main.css">
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; padding: 16px; background: #f5f5f5; }
    h1 { font-size: 18px; margin-bottom: 12px; }
    nav { display: flex; gap: 8px; margin-bottom: 16px; align-items: center; }
    .nav-label { font-size: 13px; color: #666; margin-right: 4px; }
    .nav-btn {
      padding: 6px 16px; border-radius: 6px; font-size: 14px; text-decoration: none;
      border: 1px solid #ccc;
    }
    .nav-btn.active { background: #fff; font-weight: 600; border-color: #999; color: #111; pointer-events: none; }
    .nav-btn:not(.active) { background: #e9e9e9; color: #333; }
    .nav-btn:not(.active):hover { background: #f0f0f0; }
    .badge { display: inline-block; font-size: 11px; padding: 1px 7px; border-radius: 10px; margin-left: 6px; vertical-align: middle; }
    .badge-cdn { background: #e3f2fd; color: #1565c0; }
    .badge-local { background: #e8f5e9; color: #2e7d32; }
    .info { background: #fff3cd; border: 1px solid #ffc107; border-radius: 6px; padding: 12px 16px; margin-bottom: 16px; font-size: 14px; line-height: 1.5; }
    .info strong { color: #856404; }
  </style>
</head>
<body>
  <h1>[Short description of what is being tested]</h1>

  <nav>
    <span class="nav-label">Version:</span>
    <a class="nav-btn" href="dev-latest.html">Released <span class="badge badge-cdn">v__RELEASED_VERSION__</span></a>
    <span class="nav-btn active">PR Build <span class="badge badge-local">local</span></span>
  </nav>

  <div class="info">
    <strong>How to test:</strong> [Step-by-step reproduction instructions]
  </div>

  <div id="hot-container"></div>

  <script src="dist/handsontable.full.js"></script>
  <script>
    // window.hot is exposed for browser console / DevTools debugging
    window.hot = new Handsontable(document.getElementById('hot-container'), {
      // === ADAPT THIS CONFIG TO THE TEST CASE ===
      data: Handsontable.helper.createSpreadsheetData(10, 6),
      colHeaders: true,
      rowHeaders: true,
      width: '100%',
      height: 320,
      themeName: 'ht-theme-main',
      licenseKey: 'non-commercial-and-evaluation',
    });
  </script>
</body>
</html>

Filling in the templates

Replace these placeholders in both files:

PlaceholderValue
__RELEASED_VERSION__The latest published version from handsontable/package.json (e.g., 17.0.1). If the PR branch bumped the version, use the version from the base branch (git show origin/develop:handsontable/package.json).
[Short description...]A one-line summary, e.g., "Filters dropdown closes on Android touch"
[Step-by-step reproduction...]Numbered steps the reviewer should follow, e.g., "1. Double-tap cell A1. 2. The editor should open and stay open."

Keep the <script> config identical in both files — the only difference between the pages is which Handsontable build they load. That way any behavioral difference the reviewer sees is caused by the code change, not a config discrepancy.

Adapting the config

Tailor the Handsontable config block based on what the PR changes:

Bug fix PRs — Configure the grid to reproduce the bug. dev-latest.html should exhibit the broken behavior; dev-pr.html should show it fixed. Include the minimal settings needed to trigger the issue.

Feature PRs — Configure the grid to showcase the new feature. dev-latest.html shows the "before" state (feature absent); dev-pr.html demonstrates the new capability.

Plugin-specific — Enable the relevant plugin with settings that exercise the changed code paths. Example for Filters:

window.hot = new Handsontable(document.getElementById('hot-container'), {
  data: Handsontable.helper.createSpreadsheetData(20, 6),
  colHeaders: true,
  rowHeaders: true,
  dropdownMenu: true,
  filters: true,
  width: '100%',
  height: 400,
  themeName: 'ht-theme-main',
  licenseKey: 'non-commercial-and-evaluation',
})

Touch/mobile testing — Keep the grid width responsive (width: '100%'). Mention touch-specific steps in the instructions.

Third-party integrations — If the demo needs external libraries (flatpickr, Pickr, etc.), load them from CDN in both files. Keep versions consistent.

Additional CSS and external libraries

Add them to the <head> of both files:

<!-- Example: flatpickr for date editor testing -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>

Step 4 — Serve and verify

Start a local server from the handsontable/ directory:

python3 -m http.server 8767 --directory handsontable &

Verify both files are reachable:

curl -s -o /dev/null -w "dev-latest: %{http_code}\n" http://localhost:8767/dev-latest.html && \
curl -s -o /dev/null -w "dev-pr:     %{http_code}\n" http://localhost:8767/dev-pr.html

Tell the user both URLs:

  • http://localhost:8767/dev-latest.html — Released version (CDN)
  • http://localhost:8767/dev-pr.html — PR Build (local)

The nav bar in each file links to the other, so the reviewer can switch back and forth without re-typing URLs.

Important notes

  • Both files are gitignored (dev*.html) — they will not appear in git status or get committed.
  • Each file loads only one Handsontable build — no dual-instance tricks needed.
  • If the released version used the old CSS system (pre-v17, dist/handsontable.full.css), adjust the CDN CSS link in dev-latest.html accordingly.
  • For very old version comparisons, check that the API used in the config block exists in both versions.

Signals

GitHub stars
22k
Forks
3k
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
demo-page
Source
github.com/handsontable/handsontable