Skip to content

chore: adopt React 19 context/ref APIs and enforce via ESLint - #2911

Open
brandon-pereira wants to merge 2 commits into
mainfrom
brandon/brandon-eslint-react19-context-ref
Open

chore: adopt React 19 context/ref APIs and enforce via ESLint#2911
brandon-pereira wants to merge 2 commits into
mainfrom
brandon/brandon-eslint-react19-context-ref

Conversation

@brandon-pereira

Copy link
Copy Markdown
Member

What

Migrates the app to React 19's context and ref conventions, then promotes the corresponding @eslint-react rules to error so they can't regress.

Rule Sites Migration
no-context-provider 17 <XContext.Provider><XContext>
no-use-context 16 useContext(X) / React.useContext(X)use(X) / React.use(X)
no-forward-ref 3 forwardRef((props, ref) => …)({ …props, ref }) => … (ref-as-prop)

The three forwardRef conversions are Tile (DBDashboardPage), EventRow (SessionEventList), and ValueRenderer (HyperJson).

Why

React 19 is in place (react@^19.2.3). These are the idiomatic replacements for the legacy context/ref APIs, and enforcing them as error stops the old patterns from creeping back in. This continues the lint-warning cleanup started in #2903.

Enforcement

  • @eslint-react/no-context-provider, no-use-context, no-forward-referror in packages/app/eslint.config.mjs
  • App --max-warnings lowered 601 → 565

Verification

  • yarn lint (app) — pass (0 errors; 3 rules now enforced as error)
  • tsc --noEmit (app) — clean
  • yarn knip — clean (removed forwardRef import orphaned nothing)
  • yarn ci:unit173 suites / 2820 tests pass (behavioral migrations verified, including the 3 touched test files)

Behavior is unchanged. Changeset included (@hyperdx/app patch).

@vercel

vercel Bot commented Aug 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperdx-oss Ready Ready Preview Aug 14, 2026 3:53pm
hyperdx-storybook Ready Ready Preview Aug 14, 2026 3:53pm

Request Review

@changeset-bot

changeset-bot Bot commented Aug 14, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ee74575

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@hyperdx/app Patch
@hyperdx/api Patch
@hyperdx/otel-collector Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@greptile-apps

greptile-apps Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR migrates the application to React 19 context and ref conventions and configures ESLint to prevent legacy APIs from returning.

  • Replaces context providers and consumers with React 19 equivalents.
  • Converts three forwardRef components to ref-as-prop components.
  • Promotes the corresponding ESLint rules to errors and lowers the warning ceiling.
  • Adds a patch changeset for @hyperdx/app.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failure remains.

No blocking failure remains.

Important Files Changed

Filename Overview
packages/app/src/DBDashboardPage.tsx Converts Tile to React 19 ref-as-prop semantics while retaining the ref on its outer element.
packages/app/src/SessionEventList.tsx Converts the virtualized EventRow component to accept and attach its measurement ref as a prop.
packages/app/src/components/HyperJson.tsx Converts ValueRenderer to ref-as-prop while preserving the referenced span targets.
packages/app/eslint.config.mjs Enforces the React 19 context and ref conventions as lint errors.
packages/app/package.json Lowers the application ESLint warning ceiling to 564.

Reviews (3): Last reviewed commit: "fix(dashboards): restore release markers..." | Re-trigger Greptile

Migrate the app to React 19's context and ref conventions, then promote the
corresponding lint rules to error so they can't regress:

- Render <Context> directly instead of <Context.Provider> (17 sites)
- Use the `use` hook instead of `useContext` (16 sites)
- Pass `ref` as a regular prop instead of wrapping in forwardRef (3 sites)

Promotes @eslint-react/no-context-provider, no-use-context, and no-forward-ref
to error, and lowers the app --max-warnings ceiling from 601 to 565.

Behavior is unchanged: 2820 unit tests pass, tsc and knip are clean.
@brandon-pereira
brandon-pereira force-pushed the brandon/brandon-eslint-react19-context-ref branch from f789353 to 6865069 Compare August 14, 2026 15:34
@github-actions github-actions Bot added the review/tier-4 Critical — deep review + domain expert sign-off label Aug 14, 2026
@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

🔴 Tier 4 — Critical

Touches authentication, tenancy data models, the public API or shipped database config — or substantially changes the query rendering engine, background tasks, the OTel pipeline, image build, or release CI.

Why this tier:

  • Large diff: 2319 production lines changed (threshold: 1000)

Review process: Deep review from a domain expert. Synchronous walkthrough may be required.
SLA: Schedule synchronous review within 2 business days.

Stats
  • Production files changed: 29
  • Production lines changed: 2319 (+ 12 in test files, excluded from tier calculation)
  • Branch: brandon/brandon-eslint-react19-context-ref
  • Author: brandon-pereira

To override this classification, remove the review/tier-4 label and apply a different review/tier-* label. Manual overrides are preserved on subsequent pushes.

@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Deep Review

✅ No critical issues found.

This is a mechanical React 19 context/ref API migration. Every behavioral change was inspected directly: the three forwardRef → ref-as-prop conversions (Tile, EventRow, ValueRenderer) wire ref correctly as a destructured prop onto the same DOM node as before; all <XContext.Provider value=…><XContext value=…> and useContext(X)/React.useContext(X)use(X)/React.use(X) swaps are behavior-preserving; and React.memo(ValueRenderer) receives a stable useRef object (valueRef), so folding ref into memo's shallow comparison introduces no extra renders. The head also carries a follow-up commit that restores dashboard release markers dropped by the initial mechanical pass, so that regression is already closed in this diff.

🟡 P2 -- recommended

  • packages/app/src/components/HyperJson.tsx:54 -- The three forwardRef removals change how refs flow through Tile, EventRow, and ValueRenderer, but no test attaches or asserts a ref on any of them; one migration regression (dashboard release markers) already slipped through the same mechanical pass undetected.
    • Fix: Add a focused test that mounts each converted component with a ref and asserts the ref resolves to the expected DOM node.
    • ce-testing-reviewer, ce-adversarial-reviewer
🔵 P3 nitpicks (2)
  • packages/app/src/DBDashboardPage.tsx:138 -- The three converted components type the ref prop inconsistently: Tile uses ForwardedRef<HTMLDivElement> while EventRow and ValueRenderer use React.Ref<…>; for a plain ref-as-prop component React.Ref is the conventional type.
    • Fix: Standardize all three ref props on React.Ref<…>.
  • packages/app/src/components/HyperJson.tsx:54 -- Dropping forwardRef removes the implicit display name these components had in React DevTools, leaving ValueRenderer's inner arrow anonymous under memo.
    • Fix: Assign the memoized arrow to a named identifier (or set displayName) so it remains labeled in DevTools.

Reviewers (7): ce-correctness-reviewer, ce-kieran-typescript-reviewer, ce-julik-frontend-races-reviewer, ce-maintainability-reviewer, ce-testing-reviewer, ce-adversarial-reviewer, ce-project-standards-reviewer.

Testing gaps: Ref forwarding for the three forwardRef-converted components (Tile, EventRow, ValueRenderer) is unverified by any test.

@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

E2E Test Results

All tests passed • 291 passed • 1 skipped • 936s

Status Count
✅ Passed 291
❌ Failed 0
⚠️ Flaky 3
⏭️ Skipped 1

Tests ran across 4 shards in parallel.

View full report →

The React 19 context/ref migration accidentally dropped the dashboard
release-markers feature while rewriting DBDashboardPage.tsx, contradicting
its "behavior unchanged" claim. A user opening the dashboard menu lost the
"Show/Hide release markers" toggle, and a shared URL carrying
releaseMarkers=true silently rendered no release annotations.

Restore the wiring on top of the migrated file (without reverting the
migration):
- useReleaseAnnotations + mergeAnnotations imports and the releaseMarkers
  URL query state
- the showReleaseAnnotations Tile prop, the per-tile releaseAnnotations
  query, and the merged annotations passed to the chart
- the toggle-release-annotations menu item

Also restores the tileCanDrawAnnotations guard on alertAnnotations, which
the migration had likewise dropped. This re-gives useReleaseAnnotations and
mergeAnnotations a production consumer, so their tests are no longer
orphaned.
kodiakhq Bot pushed a commit that referenced this pull request Aug 14, 2026
…2912)

## What

Renames the 10 `useRef` values flagged by `@eslint-react/naming-convention/ref-name` to end in `Ref`, then promotes the rule to `error` so the convention holds going forward.

| File | Refs renamed |
|---|---|
| `DOMPlayer.tsx` | `wrapper`, `playerContainer`, `replayer`, `initialEvents`, `setLastEventTsLoaded` → `*Ref` |
| `DBEditTimeChartForm/EditTimeChartForm.tsx` | `subFormDirty`, `autoRunFired` → `*Ref` |
| `hooks/useMetadata.tsx` | `settingsApplied` → `settingsAppliedRef` |
| `sessions.ts` | `lastAbortController` → `lastAbortControllerRef` |
| `utils.ts` | `observer` → `observerInstanceRef` |

## Why

Consistent `Ref` naming makes ref values obvious at a glance. This is the next mechanical batch in the ongoing lint-warning cleanup (follows #2903 and #2911).

## Notes

- In `utils.ts`, the intersection-observer hook already had an `observerRef` **callback** ref, so the `useRef` was renamed to `observerInstanceRef` to avoid a redeclaration collision (rather than the mechanical `observerRef`).
- `DOMPlayer.tsx`'s diff is larger only because `replayer` had 39 usages — all pure renames. `styles.playerContainer` (a CSS class) was deliberately left untouched.

## Enforcement

- `@eslint-react/naming-convention/ref-name` → **`error`**
- App `--max-warnings` lowered **601 → 590**

## Verification

- `yarn lint` (app) — pass (0 errors; rule now enforced as error)
- `tsc --noEmit` (app) — clean
- `yarn knip` — clean
- `yarn ci:unit` — **174 suites / 2898 tests pass**

Behavior is unchanged. Changeset included (`@hyperdx/app` patch).
@brandon-pereira
brandon-pereira requested review from a team, knudtty and wrn14897 and removed request for a team and knudtty August 14, 2026 18:37
kodiakhq Bot pushed a commit that referenced this pull request Aug 14, 2026
…ules (#2913)

## What

Clears the last small batch of api ESLint warnings and promotes their rules to `error`.

| Rule | Count | Resolution |
|---|---|---|
| `@typescript-eslint/no-namespace` | 2 | Merge the duplicate Express `declare global` namespace blocks; scoped disable (pattern is required) |
| `@typescript-eslint/no-empty-object-type` | 1 | Scoped disable on `interface User extends UserDocument {}` (required for Express `User` augmentation) |
| `n/no-process-exit` | 5 | Config override: rule `off` for the two process entry-point files |

## Why

These are all **legitimate patterns the linter flags**, not real issues:

- **Express type augmentation** (`namespace Express { interface User … }`) is the documented way to extend Express's types — interface merging is required, and a type alias / non-empty interface can't do it. I merged the two duplicate `namespace Express` blocks into one for tidiness and disabled the two rules on that single augmentation block with an explanatory comment.
- **`process.exit()`** in `src/index.ts` (startup failure, last-resort `uncaughtException`/`unhandledRejection` handlers) and `src/tasks/index.ts` (CLI task completion/failure) is the intended behavior. Rather than 5 inline disables, the rule is scoped `off` for just those two entry-point files — so it still guards against stray `process.exit()` elsewhere in the api.

Promoting all three to `error` turns these into guardrails: new violations outside the sanctioned spots now fail lint. This is the last of the small mechanical/one-off batches (follows #2903, #2911, #2912).

## Verification

- `yarn lint` (api) — pass (0 errors; 3 rules now enforced as error)
- `tsc --noEmit` (api) — clean (Express augmentation still resolves after the merge)
- `yarn knip` — clean
- api `--max-warnings` lowered **311 → 302**

Behavior is unchanged. Changeset included (`@hyperdx/api` patch).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review/tier-4 Critical — deep review + domain expert sign-off

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant