Skip to content

Type the render client: no implicit any, no implicit this#177

Merged
Alek99 merged 1 commit into
feat/ts-vite-clientfrom
feat/ts-strict-client
Jul 22, 2026
Merged

Type the render client: no implicit any, no implicit this#177
Alek99 merged 1 commit into
feat/ts-vite-clientfrom
feat/ts-strict-client

Conversation

@Alek99

@Alek99 Alek99 commented Jul 22, 2026

Copy link
Copy Markdown
Member

Stacked on #159 (base: feat/ts-vite-client). Merge that first.

What

Turns the TypeScript conversion from "compiles" into "typechecked". 2321 diagnostics → 0 under the newly enabled flags:

noImplicitAny  noImplicitThis  strictFunctionTypes  strictBindCallApply
noImplicitReturns  noFallthroughCasesInSwitch  useUnknownInCatchVariables

Every parameter, local, and this in the client is now typed — no value acquires any implicitly. tsc already gates every build (js/build.mjs runs it first, since oxc strips types without checking them), so this is enforced on every PR.

How

  • js/src/05_types.ts (new) carries the wire contract the kernel emits: ChartSpec, TraceSpec, AxisSpec, ChannelSpec, ColumnMeta, GpuTrace, Hit, Row, Comm, TickResult, Theme, MarkKind, Rgba. Deliberately permissive where the wire is permissive — open index signatures stay, because tightening one is a compatibility decision to make alongside the Python that emits it.
  • as ThisType<ChartView> on the prototype-augmentation literals (51–57) types this as ChartView instead of the object literal. That one type assertion resolved 1190 of the 2321 diagnostics, and erases at compile.
  • Per-module types where no shared name fits: LOD drill/density updates, animation easings and transition records, view-state patches and durable state, interaction option bags, GL program/buffer tags (XyProgram, XyBuffer), ColumnView, AxisMap.
  • _listen became generic over its event type so call sites keep precise handlers (PointerEvent, WheelEvent, …) under strictFunctionTypes.

Types-only — proven, not asserted

The committed minified bundles in python/xy/static/ are byte-identical before and after this change; the diff contains no bundle. Independently, three of the workers transpiled HEAD vs. working copies and byte-compared the emitted JS per file.

That check earned its keep twice: it caught a &&?. rewrite that had slipped into a catch-narrowing fix, and a case where an interface placed above the first runtime statement absorbed a file's header comment and changed emit.

Verification: tsc clean · bundles byte-identical · full pytest 2208 passed · render smoke XY_OK with pixel counts and context-loss hashes identical to the pre-typing run · ruff + pre-commit clean.

Test fallout

Source-marker tests that grep exact client signatures are updated to the typed forms, preserving their parameter-contract intent. Separately, tests/reflex_adapter/test_assets.py still grepped unminified markers (function renderStandalone() — it only runs when reflex is installed, so it was missed when the bundles began shipping minified in #159. Fixed here to use the same export-alias contract verify_wheel.py asserts.

Latent bugs surfaced (reported, not fixed — types-only)

Typing the code found real issues. None are touched here; each deserves its own change:

  1. lodApplyDensityUpdate reads g.density.colormap/.color/.lut unguarded, while every sibling access guards — a density update on a trace whose density was never built is an uncaught TypeError.
  2. RECT_MARK/BAR_MARK/SEGMENT_MARK/AREA_MARK/line dereference g.trace.style.color unguarded; style is optional on the wire (MESH_MARK and hexbin do guard).
  3. 54_kernel builds pendingTraceIds with Number(upd.id) but tests membership with the raw g.trace.id; TraceSpec.id is string | number, so a string id silently skips the clearPending sweep.
  4. _onA11yKey computes hit.trace * 1e9 + hit.index from a trace id; a string id makes _hoverId NaN and every !== -1 guard read true forever.
  5. 51_annotations liftBounds.width is only safe via a two-expression short-circuit that a future edit could split.
  6. 57_viewstate canBandPan() returns truthy via _axisContained even when pan policy excludes the axis — possibly deliberate, worth a look.

Items 1, 2, and 5 are exactly the class strictNullChecks would catch, which motivates the order below.

Deferred (documented in js/tsconfig.json)

  1. Remove ChartView's [key: string]: any. It is what still lets this.typo compile and what stops the annotated signatures in 51–57 from being enforced at call sites. Needs ~403 declared members (172 class methods, 119 augmentation methods via declare module merging, 112 instance fields) — mechanical, but its own reviewable PR.
  2. strictNullChecks — largest remaining win, largest diff.
  3. strict: true once 1 and 2 land.

Enables noImplicitAny, noImplicitThis, strictFunctionTypes,
strictBindCallApply, noImplicitReturns, noFallthroughCasesInSwitch, and
useUnknownInCatchVariables on js/tsconfig.json, and annotates the client to
satisfy them: 2321 diagnostics -> 0. Every parameter, local, and `this` in
the client is now typed; no value acquires `any` implicitly.

New js/src/05_types.ts carries the wire contract the kernel emits (ChartSpec,
TraceSpec, AxisSpec, ChannelSpec, ColumnMeta, GpuTrace, Hit, Row, Comm,
TickResult, Theme, MarkKind, Rgba). It is deliberately permissive where the
wire is permissive: open index signatures stay, because tightening one is a
compatibility decision to make alongside the Python side that emits it.
Per-module types cover shapes with no shared name (LOD drill/density updates,
animation easings, view-state patches, interaction option bags, GL program
and buffer tags).

The prototype-augmentation modules (51-57) get `as ThisType<ChartView>` on
their Object.assign literals, so `this` is a ChartView inside those methods
instead of the literal — that alone resolved 1190 of the diagnostics and, being
a type assertion, erases at compile.

Types-only by construction and by proof: the committed minified bundles in
python/xy/static are byte-identical before and after, so the shipped client is
unchanged. The render smoke reports XY_OK with pixel counts and context-loss
hashes identical to the pre-typing run.

Source-marker tests that grep exact client signatures are updated to the typed
forms, preserving their parameter-contract intent. tests/reflex_adapter/
test_assets.py additionally moves off unminified markers - it only runs when
reflex is installed, so it was missed when the bundles began shipping minified.

Deferred, documented in js/tsconfig.json with the reasons and order:
removing ChartView's [key: string]: any index signature (needs ~403 declared
members) and strictNullChecks, then strict: true.
@codspeed-hq

codspeed-hq Bot commented Jul 22, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 97 untouched benchmarks
⏩ 1 skipped benchmark1


Comparing feat/ts-strict-client (99cadae) with feat/ts-vite-client (6caeeb0)

Open in CodSpeed

Footnotes

  1. 1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports.

@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds full implicit-type checking to the TypeScript render client. The main changes are:

  • Shared types for the kernel wire contract and render state.
  • Explicit types for client functions, callbacks, events, and prototype methods.
  • Stronger TypeScript compiler checks in the client build.
  • Updated source and bundle contract tests for typed and minified output.

Confidence Score: 5/5

This looks safe to merge.

No blocking issues found in the changed code.

T-Rex T-Rex Logs

What T-Rex did

  • Ran the TypeScript typecheck proof and confirmed it completed with exit code 0, validating the TypeScript step of the render-client workflow.
  • Ran the render-client bundle freshness proof and confirmed it completed with exit code 0, validating the bundle freshness check.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
js/src/05_types.ts Adds permissive shared declarations for wire data, rendering state, communication, and mark implementations.
js/src/45_lod.ts Adds explicit types for LOD windows, density payloads, drill updates, and GPU cache helpers.
js/src/50_chartview.ts Types ChartView construction, context management, events, view state, rendering helpers, and dynamic members.
js/src/60_entries.ts Types the public rendering entry points while preserving runtime exports and prototype side effects.
js/tsconfig.json Enables implicit-type, callback variance, bind/call, return-path, fallthrough, and catch-variable checks.
tests/reflex_adapter/test_assets.py Changes installed-bundle checks to use stable minified export aliases.
tests/test_static_client_security.py Updates source markers to match explicit TypeScript signatures while preserving the existing contracts.

Reviews (1): Last reviewed commit: "Type the render client: no implicit any,..." | Re-trigger Greptile

Alek99 commented Jul 22, 2026

Copy link
Copy Markdown
Member Author

Docs verification passed on 99cadae3cfbbc59de0b63ce09145351154600259.

  • docs/app suite: 72 passed, 1 expected xfail
  • Production docs build completed successfully.
  • Sitemap, Markdown-asset, and generated-HTML route validators passed.
  • All 57 published /docs/xy sitemap routes returned HTTP 200 from the production server.
  • Browser smoke test rendered the landing page’s interactive charts (12 canvases) with no console errors.

The PR changes only the typed render client; the documentation site remains healthy.

@Alek99
Alek99 merged commit 044edb6 into feat/ts-vite-client Jul 22, 2026
24 checks passed
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.

1 participant