Skip to content

fix(server-examples): compile at Handsontable 18.1.0 and refresh locks (DEV-2731) - #294

Merged
demtario merged 2 commits into
masterfrom
fix/DEV-2731-server-examples-18-1
Sep 2, 2026
Merged

fix(server-examples): compile at Handsontable 18.1.0 and refresh locks (DEV-2731)#294
demtario merged 2 commits into
masterfrom
fix/DEV-2731-server-examples-18-1

Conversation

@demtario

@demtario demtario commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes DEV-2731. Same defect class as DEV-2727 (#281 / #282), which fixed examples/ and deliberately left server-examples/ out of scope.

The break

18.1.0 rewrote ColumnSettings from Omit<GridSettings, 'data'> to Omit<RemoveIndexSignature<GridSettings>, 'data'>. Under 18.0.0 that Omit collapsed against GridSettings's [key: string]: any, so every named column option resolved to any. Now they are real types, and an unannotated settings holder widens its literals past them.

Two coupled problems: the frontends do not compile at 18.1.0, and all 21 are still held at 18.0.0 by their committed lock (the ^18.0.0 range already admits 18.1.0). So npm ci is green today and the break lands the moment anyone refreshes a lock. Fixing and bumping therefore had to land together — splitting them would leave a commit where the locks are refreshed and 7 Angular projects do not compile, which nothing in CI would catch.

What the builds actually showed

Measured with raw npm ci && npm run build per project, asserting on exit codes.

pre-fix @ 18.1.0 post-fix
7 Angular 3 red (dateFormat), 4 green 7 green
7 React green — but only because of as any 7 green, cast removed
7 vanilla green (plain JS) 7 green

21/21 pass. express/server and nestjs/server are excluded: no build script, no Handsontable dependency.

Three corrections to the ticket, all from builds rather than reading:

It is 21 frontends, not 23. The ticket counted the two backend package.json files.

The Angular check surface is columns[] and nothing else. The wrapper defines its own interface GridSettings extends Omit<Handsontable.GridSettings, 'columns' | 'data'>, and that plain Omit still collapses against the index signature — so every top-level option stays any. Only columns is redeclared, and only ColumnSettings received RemoveIndexSignature at 18.1.0. That is why 4 of the 7 Angular projects were never broken, and why annotating the holder surfaced nothing beyond columns[].dateFormat.

The React frontends were not safe. The ticket lists them as shielded by {...(settings as any)}. That cast was hiding the same defect, not avoiding it: const settings = useMemo(() => ({...})) gets no contextual typing, so the literal widens exactly like the Angular holder, and tsc -b already runs in their build.

The fix

Angular — annotate the holder as GridSettings, mirroring #281's idiom (import { HotTableModule, type GridSettings }). One error, in the 3 projects carrying an Intl dateFormat object.

React — annotate the memo as HotTableProps and drop the cast. HotTableProps, not GridSettings: HotTable takes settings as individual props, and HotTableProps extends ReplaceRenderersEditors<GridSettings> maps over the type and does drop the index signature. So top-level options are genuinely checked there — which surfaced exactly one error per file: beforeRowsMutation's narrowed 'create' | 'update' | 'remove' parameter rejected contravariantly against the declared (operation: string, ...). Widened to string; every body only tests operation === 'remove', so this is behavior-neutral.

No config value changed. The dateFormat objects were already valid Intl literals ('numeric', '2-digit') — the error was pure literal widening, cured by the annotation. The diff is entirely type-level and no runtime behavior moves in any of the 21 projects. Neither judgment call this could have required (an option silently ignored for years, an Intl value that needed correcting) actually arose.

The vestigial dropdownMenu: [...] as any casts are removed — verified by build in both flavours, including React where top-level is checked.

Locks — all 21 refreshed 18.0.0 → 18.1.0 via npm update <pkgs> --package-lock-only. package.json is untouched: ^18.0.0 already admits 18.1.0, and the lock was what pinned these back. (npm install --package-lock-only preserves an already-satisfying resolution — the npm counterpart of the pnpm trap #281 documented.)

CI coverage

Nothing in CI built this tree, which is how both this and DEV-2727 shipped unnoticed. Adds .github/workflows/server-examples-build.yml:

  • build — deterministic, per-project matrix, npm ci. Guards source edits. By construction it can never see a new upstream release.
  • build-latest — weekly canary, installs handsontable@latest before building. This is the job that would have caught 18.1.0 the day it shipped. Expected to go red on a breaking release; that is the signal.

The matrix is discovered at run time, so adding a backend or frontend needs no edit here. npm ls asserts rather than reports (exit 1 on a tree that does not satisfy package.json) — a stale or hand-edited lock is the one failure build cannot otherwise see.

Two things a reviewer should know

The Angular beforeRowsMutation widening is forward-looking hardening, not part of the fix. Those 7 files compile identically before and after, because the property is any there. I widened it anyway so the paired demos stay identical and so a future wrapper release applying RemoveIndexSignature to GridSettings — as ColumnSettings already got — cannot re-break them. Do not hunt for the Angular error it addresses; there isn't one yet.

The workflow has never run under Actions. Two things only a real run proves: fromJSON expanding into a 21-entry matrix, and per-project cache-dependency-path resolving. The canary's step body is proven able to fail — I ran it verbatim against pre-fix source and got exit 1. Worth noting two earlier attempts at that proof came back falsely green (a latent project, then reverted React whose as any shield returns), which is exactly why the negative control matters.

Verification

  • npm ci && npm run build across all 21 frontends: 21/21 green. Run raw, not through rtk — its filters have previously fabricated a pass summary over real TypeScript errors.
  • Negative control recorded red before the fix (express/client-angular, TS2322 … 'dateFormat.year' … Type 'string' is not assignable to type '"numeric" | "2-digit" | undefined').
  • Every lock confirmed to have actually moved; zero package.json drift.
  • Discover logic dry-run: exactly 21 projects, both servers excluded.

Master only. server-examples/ on prod-examples/18 is already a commit behind (DEV-2548 was never ported there), so that tree is master-only by convention; reconciling it deserves its own ticket.


Note

Low Risk
Changes are limited to example apps, lockfile bumps, and CI; runtime behavior is described as unchanged aside from compiling against tighter public types.

Overview
Adds CI for server-examples/ via server-examples-build.yml: a runtime-discovered matrix runs npm ci and npm run build on every frontend with a lockfile and build script; PRs get that deterministic job only, while weekly/dispatch build-latest reinstalls handsontable@latest (and the framework wrapper) to catch upstream type breaks.

Handsontable 18.1.0 is applied across all 21 example frontends by refreshing package-lock.json (core and @handsontable/* wrappers); package.json ranges are unchanged.

Angular demos type the table config as GridSettings so stricter ColumnSettings in 18.1.0 can compile; beforeRowsMutation uses operation: string to match the hook signature, and vestigial dropdownMenu as any casts are removed.

React demos use useMemo<HotTableProps>, spread props into HotTable without as any, and apply the same beforeRowsMutation and dropdownMenu typing cleanup.

Reviewed by Cursor Bugbot for commit 51cea5d. Bugbot is set up for automated code reviews on this repo. Configure here.

…s (DEV-2731)

18.1.0 rewrote ColumnSettings from Omit<GridSettings, 'data'> to
Omit<RemoveIndexSignature<GridSettings>, 'data'>. Under 18.0.0 that Omit
collapsed against GridSettings's [key: string]: any index signature, so every
named column option resolved to any. Now they are real types, and an
unannotated settings holder widens its literals past them.

Angular: annotate the holder as GridSettings, mirroring the examples/ fix in
DEV-2727 (PR #281). Only columns[] is actually checked — the wrapper's own
GridSettings still extends a plain Omit over the index signature, so top-level
options remain any. The single error was columns[].dateFormat widening to
string.

React: the {...(settings as any)} spread was hiding the same defect rather
than avoiding it. Annotate the memo as HotTableProps and drop the cast.
HotTableProps maps over GridSettings and does drop the index signature, so
top-level options are checked there — which surfaced beforeRowsMutation's
narrowed 'create' | 'update' | 'remove' parameter being rejected
contravariantly against the declared (operation: string, ...). Widened to
string in all 14 TS frontends; the bodies only test operation === 'remove',
so this is behavior-neutral.

No config values changed: the dateFormat objects were already valid Intl
literals, so the fix is entirely type-level and no runtime behavior moves.
The vestigial dropdownMenu `as any` casts are removed, verified by build.

All 21 package-lock.json files refreshed 18.0.0 -> 18.1.0 via
`npm update <pkgs> --package-lock-only`. package.json ranges are untouched:
^18.0.0 already admits 18.1.0, and the lock was what pinned these back.

Adds .github/workflows/server-examples-build.yml, since nothing in CI built
this tree — which is how both this and DEV-2727 shipped unnoticed. A
deterministic job guards source edits, and a weekly canary installs
handsontable@latest so the next breaking release is caught on the day it
ships rather than at the next lock refresh.

Verified: npm ci + npm run build across all 21 frontends, raw (not via rtk,
whose filters have previously fabricated a pass summary over real TS errors).
21/21 green; both express/server and nestjs/server have no build script.
Negative control recorded red before the fix, and the canary's own step body
proven to exit 1 against pre-fix source.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3aa26d2. Configure here.

Comment thread .github/workflows/server-examples-build.yml
Actions runs `bash -e -o pipefail`, so the while loop's exit status is that
of its final iteration. With `node ... && echo`, a project that sorts last
and legitimately has no build script short-circuits the `&&`, leaving the
loop — and therefore the whole pipeline and the assignment — non-zero.

Green today only because symfony/frontend-react happens to sort last and has
a build script. Adding a backend that sorts after it would turn a planned
skip into a red workflow with no visible cause.

Reproduced with a trailing skip under `bash -e -o pipefail` (exit 1), and
confirmed the `if` form exits 0 while still discovering all 21 projects.

Reported by Cursor Bugbot on #294.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@demtario
demtario merged commit 0dc57ed into master Sep 2, 2026
72 checks passed
@demtario
demtario deleted the fix/DEV-2731-server-examples-18-1 branch September 2, 2026 10:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants