Skip to content
cccskills
FreePublished on GitHub

handsontable-dev

Work on Handsontable core bugs, features, and TypeScript types using its architecture and implementation conventions. The skill directs tasks to specialist guides and outlines registration, compatibility, documentation, and testing checks for changes.

OriginalShow the original description

Use for ANY work touching the `handsontable/` core package: fixing bugs, adding features, modifying TypeScript types, removing as-casts, writing or debugging plugins, editors, renderers, validators, cell types, hooks, shortcuts, selection, helpers, index translations, or i18n. Also use for how-to questions about core internals (plugin lifecycle, coordinate systems, hook registration, TypeScript conventions). Triggers on file paths under `handsontable/src/` (excluding `3rdparty/walkontable/` which has its own skill), or when the user describes a symptom in the core grid without naming a file. This is the primary entry point for all core Handsontable development, when in doubt, load it.

Works withPlaywright
See how to install

Good for

  • Investigate bugs in the core grid
  • Add plugins, editors, or renderers
  • Fix TypeScript types and remove unnecessary casts
  • Check feature registration and required validation

Files included (1)

  • SKILL.md12.7 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Handsontable Core Development

Dispatch table

Load handsontable-code-review (architecture dimension) first for every change, then the task-specific specialist. It is the design lens (SOLID, Law of Demeter, plugin decoupling, breaking changes); the task skill is the implementation guide.

TaskSkill
Create / modify a pluginhandsontable-plugin-dev
Create / modify an editorhandsontable-editor-dev
Create / modify a rendererhandsontable-renderer-dev
Create / modify a validatorhandsontable-validator-dev
Create / modify a cell typehandsontable-celltype-dev
Write a new E2E testhandsontable-playwright-e2e (Playwright, tests/e2e/)
Maintain a legacy E2E test (*.spec.js, frozen)handsontable-e2e-testing
Write / modify unit tests (*.unit.js or *.unit.ts)handsontable-unit-testing
Make a test actually prove behaviortest-writing-discipline
Build a demo / test pagehandsontable-demo-page
Work on CSS / themeshandsontable-css-dev
Walkontable rendering enginewalkontable-dev / walkontable-testing
A rendering-engine geometry change needs a spec (viewport, overlays, sizing, scroll sync)engine tier: edit an existing walkontable Jasmine spec, or add new coverage in tests/e2e/walkontable/*.spec.ts (walkontable-dev, Testing)
Lint violationshandsontable/AGENTS.md (Lint) + handsontable/.ai/CONVENTIONS.md
Coordinate translation (physical/visual/renderable)coordinate-systems
i18n / translationsi18n-translations
Visual regression testsvisual-testing
Docs pageswriting-docs-pages

Design rules from handsontable-code-review

Full checklist: its references/architecture.md.

  • Plugin decoupling. Talk via hooks; reach another plugin only through hot.getPlugin('Name'). No circular plugin dependencies.
  • Conflict ownership. The plugin that introduces an incompatibility owns the blocking logic. For hard conflicts, call registerConflict() from src/plugins/base/conflictRegistry.ts at module load time.
  • Law of Demeter. Go through TableView or Core for Walkontable data, not this.hot.view.wt.wtTable chains.
  • Liskov. Honor the full BasePlugin contract (lifecycle methods + static properties).
  • Cascading config. New options fit cell → column → global where applicable; document table-level-only options (like data) in JSDoc.
  • Breaking changes. Keep the legacy CSS class in the DOM when renaming. Keep old API names working (no warning for legacy, one-time warning for deprecated). Keep every default setting value. Put removed hooks/options on the removed list so misuse throws.
  • Convention over configuration. Red flags: new options with the same value at every call site (make it the default), new directories outside the existing taxonomy, explicit wiring where auto-discovery works.
  • Gold standard: src/plugins/pagination/pagination.ts (plugin structure, settings validation, conflict registration, focus management).

File layout

src/plugins/{pluginName}/        index.ts, {pluginName}.ts, types.ts?, __tests__/,
                                 AGENTS.md + CLAUDE.md->AGENTS.md (required)
src/editors/{editorName}/        index.ts, {editorName}.ts
src/renderers/{rendererName}/    index.ts, {rendererName}.ts
src/validators/{validatorName}/  index.ts, {validatorName}.ts
src/cellTypes/{typeName}/        index.ts, {typeName}.ts

Unit tests are *.unit.js or *.unit.ts. New E2E is Playwright (tests/e2e/*.spec.ts); the Jasmine *.spec.js suite is frozen (edit existing specs, migrate broken ones to Playwright). .d.ts files are generated by npm run build:types into handsontable/tmp/.


TypeScript gotchas

1. Generalize the signature, add zero as casts

New code adds no as casts (as SomeType, <SomeType>value). Treat the urge to write one as a wrong type upstream. Exhaust these in order: (1) make the function/method signature generic; (2) make the shared entity generic; (3) narrow with a type guard; (4) use unknown only at a true I/O boundary. A cast is acceptable only when none apply (external/normalization boundary, known TS generic-inference gap) and carries a comment saying why. as unknown as T (double cast) is banned. Use unknown instead of any at boundaries, then narrow. After removing casts, rerun npm run test:types; some are load-bearing.

// ✗ function getFirst(items: unknown[]): SomeRow { return items[0] as SomeRow; }
// ✓ function getFirst<T>(items: T[]): T { return items[0]; }

Fix the type at its source. When the same cast repeats across consumers, make the shared structure generic with a permissive default so existing callers compile unchanged:

class TreeNode<T extends object = Record<string, unknown>> {
  data: T;
  walkDown(cb: (node: TreeNode<T>) => unknown) { /* ... */ }
}

Example: src/utils/dataStructures/tree.ts TreeNode<T>, consumed by nestedHeaders as TreeNode<HeaderNodeData>.

1a. DOM narrowing

Use isHTMLElement from src/helpers/dom/element.ts (for a Node | Element | null value) instead of x as HTMLElement, x instanceof HTMLElement, or a nodeType === Node.ELEMENT_NODE guard.

2. .d.ts files

Declarations are generated from source into handsontable/tmp/ only: hand-write no .d.ts anywhere (there is no separate types/ mirror) and edit nothing under tmp/. If a type is missing from the public API, fix the JSDoc/export in the .ts source and rerun npm run build:types.

3. import type for types

import type { HotInstance } from '../../core/types';

4. Import shared types from core/

TypeLocation
GridSettings, Events, HookKeysrc/core/settings.ts
HotInstancesrc/core/types.ts
Plugin-local typesthe plugin's own types.ts

Import these types; never paste a partial copy of GridSettings/HotInstance into the file you are editing (a local copy drifts from the real signature). When the existing type is too wide, narrow it with a generic parameter or type guard at the boundary. Consumers import the same types from the package (import type { GridSettings } from 'handsontable'); see docs/content/guides/tools-and-building/typescript-types/typescript-types.md.

Adding a new hook:

  1. Add the callback signature to GridSettings in src/core/settings.ts.
  2. Register the hook name in src/core/hooks/constants.ts / src/core/hooks/index.ts.
  3. Call Hooks.getSingleton().register('myHook') at module level in the plugin file.

5. Private fields use #, callbacks are arrow-function class fields

class MyPlugin extends BasePlugin {
  #map: HidingMap | null = null;
  #onAfterRender = (): void => { /* `this` is bound */ };
}

@private JSDoc tags and .bind(this) are forbidden.

6. Cognitive complexity ≤ 15 per function

Extract a helper to fix a violation.


Cross-cutting rules (enforced by ESLint)

RuleWhat to do
No throw new Error()throwWithCause('...', cause) from src/helpers/errors.ts
No window / document / console globalsthis.hot.rootWindow, this.hot.rootDocument, helpers from src/helpers/console.ts
No raw setTimeout / setIntervalthis.hot._registerTimeout(fn, delay) (auto-clears on hot.destroy())
No barrel importsImport from the specific submodule path (plugins/index, editors/index, renderers/index, validators/index, cellTypes/index, i18n/index are off limits). Only src/registry.ts may use barrels.
No direct cross-plugin importsHooks or this.hot.getPlugin('Name')
it() in *.spec.js must be asyncasync with await when calling HOT rendering APIs
No native toLocaleLowerCase(locale)localeLowerCase(value, locale) from helpers/string (enforced by no-restricted-syntax)

JSDoc

jsdoc/require-jsdoc is error for src/**/*.ts and scripts/**/*.mjs: every class, method, function declaration, and class field needs a block. Test and type files (*.unit.ts, *.spec.ts, *.types.ts, *.d.ts) are exempt.

  • Multiline format (root AGENTS.md), with a blank line before /** and after */.
  • {Type} in every @param and @returns, public or private. docs:api (jsdoc-to-markdown) reads compiled handsontable/tmp/ and prints these types in the API reference. Keep them in sync with the TS signature ({number|object} for number | { from: number; to: number }). The TS [80004] warning is suppressed for .ts source.
  • The {Type} must parse as JSDoc (catharsis), not TypeScript. A tuple breaks docs:api with Invalid type expression. Write @type {Array<number> | null} and keep the tuple on the TS annotation (#pastePlan: [number, number, number, number] | null). No PR check catches this: the docs path filter in .github/workflows/checks.yml covers docs/** and handsontable/package.json, not handsontable/src/**, so the break surfaces as a failing Docs Staging Deployment on the next unrelated push touching docs/**. After a JSDoc type change, build the package and run npm --prefix docs run docs:api.
  • Private # fields: a description block, no @private.
  • Descriptions state something new (what the member does or represents, with population/invalidation rules where relevant), not a restatement of its name. American English, active voice, short sentences.

DOM and data gotchas

  • Merged cells: read meta, not DOM. Read colspan/rowspan from hot.getCellMeta(row, col), not td.colSpan / td.rowSpan. MergeCells sets cellProperties.colspan via afterGetCellMeta; the DOM attribute may be missing outside the viewport and ignores custom afterGetCellMeta overrides.
  • Coordinates. Translate physical/visual/renderable with hot.rowIndexMapper / hot.columnIndexMapper (coordinate-systems skill).
  • i18n. Add constants to src/i18n/constants.ts and translations to every file in src/i18n/languages/.

Registration wiring

ComponentWire into
Pluginsrc/plugins/index.ts
Editorsrc/editors/index.ts + src/editors/factory.ts
Renderersrc/renderers/index.ts + src/renderers/factory.ts
Validatorsrc/validators/index.ts
Cell typesrc/cellTypes/index.ts
Any new option / hooksrc/dataMap/metaManager/metaSchema.ts + src/core/settings.ts

Build: type declarations

npm run build:types        # step 1: tsc emits tmp/**/*.d.ts
npm run downlevel:types    # step 2: rewrites post-TS-5.1 lib types to TS 5.1 equivalents

npm run build runs both. Run after any change to the public type surface (new export, changed parameter type, new GridSettings option). Wrappers consume handsontable/tmp/.

The dev compiler is TS 6, which infers ArrayIterator, IteratorObject (TS 5.6+) and WeakKey (TS 5.2+), absent from TS 5.1. scripts/downlevel-dts.mjs swaps them for IterableIterator<T> / object. CI job verify-emitted-types runs tsc@5.1.6 --noEmit against tmp/ and reports the leaked identifier as TS2304: Cannot find name '...'. Fix by either annotating the source with the TS 5.1 type (: IterableIterator<T>, WeakMap<object, V>) or adding a row to REPLACEMENTS in scripts/downlevel-dts.mjs.


Mandatory checklist for every change

  • Source file is .ts
  • No as / any casts introduced
  • No .d.ts files hand-edited
  • Unit tests (*.unit.js or *.unit.ts), pure logic, no mocks
  • E2E tests: Playwright in tests/e2e/ (new); legacy *.spec.js suite is frozen
  • npm run build (or build:types + downlevel:types) run if public types changed
  • Wired into all relevant index / factory files
  • Added to metaSchema.ts if a new option was introduced
  • JSDoc on every class, method, function declaration, and class field (multiline; {Type} in every @param/@returns, in sync with the TS signature; no @private on # fields)
  • No breaking change (or it is explicitly called out)
  • Changelog entry added (bin/changelog entry)

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Develop and modify the Handsontable Angular wrapper with guidance on component lifecycle, settings updates, and NgZone, which controls change detection. It also covers standalone examples, global configuration, and the build and test workflow.

  • Fix settings updates in the Angular wrapper
  • Review change detection around grid operations
  • Update examples to standalone components
handsontable/handsontable22.1KUpdated Oct 19, 2026

Open local or public web pages in BrowserStack Live for checks on real mobile devices or desktop browsers. The skill prepares temporary access to a local server, launches the requested session, and shares its dashboard link.

  • Check a local page on an Android device
  • Open a page in Safari on an iPhone or iPad
  • Inspect behavior in a specific desktop browser
handsontable/handsontable22.1KUpdated Oct 19, 2026

Prepare user-facing changelog entries for source code changes. The skill determines whether an entry is needed, classifies the change, writes a clear title, and uses the project CLI to create a JSON file tied to the pull request.

  • Add a changelog entry for a bug fix
  • Classify a new feature or behavior change
  • Explain a breaking change and its replacement
handsontable/handsontable22.1KUpdated Oct 19, 2026

code-graph

FreeSummary

Explore relationships between functions and files using a prebuilt code graph, a map of code dependencies. Trace bugs, inspect callers and execution paths, and assess the impact of refactoring or proposed changes.

  • Explore callers and dependencies in unfamiliar code
  • Trace a bug across multiple files
  • Assess the impact of renaming or removing code
handsontable/handsontable22.1KUpdated Oct 19, 2026

Work with Handsontable row and column positions by distinguishing source data, displayed order, and rendered cells. It guides index conversion and helps investigate misplaced data when sorting, filtering, hiding, or moving rows and columns.

  • Investigating misplaced rows or columns
  • Converting between source and displayed indexes
  • Combining filters with manual column movement
handsontable/handsontable22.1KUpdated Oct 19, 2026

Create Handsontable documentation examples for JavaScript, TypeScript, React, Angular, and Vue. The skill explains source files, generated JavaScript, required imports and license settings, embedding examples in guides, and checking that each framework version displays correctly.

  • Adding a code example to a feature guide
  • Preparing examples for multiple frameworks
  • Generating JavaScript examples from TypeScript
handsontable/handsontable22.1KUpdated Oct 19, 2026

See all skills by handsontable

Report a problem with this skill