| name | demo-page |
| description | 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. |
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:
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 = new Handsontable(document.getElementById('hot-container'), {
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 = new Handsontable(document.getElementById('hot-container'), {
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:
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:
<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.