# Demo Page

> 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.

- Skill: `handsontable/demo-page` (Agent Skill)
- Install (CLI): `npx skillmds@latest add handsontable/demo-page`
- Raw SKILL.md: https://api.skillmd.com/api/skills/handsontable/demo-page/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: handsontable (https://skillmd.com/u/handsontable)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/handsontable/demo-page

---


# Demo Page Generator

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

| File | Loads from | Purpose |
|------|-----------|---------|
| `handsontable/dev-latest.html` | jsDelivr CDN (latest published version) | Shows the current/buggy behavior |
| `handsontable/dev-pr.html` | Local `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:

```bash
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:
>
> ```bash
> 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)

```html
<!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)

```html
<!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**:

| Placeholder | Value |
|-------------|-------|
| `__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:

```js
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:

```html
<!-- 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:

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

Verify both files are reachable:

```bash
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.

