Genie React
Tool reference

Renders, effects, and profiling

Measure React work, render causes, effect schedules, errors, and before/after changes.

The JSON examples below show selected fields from the full response.

react_clear_renders

Clears render counters and starts a new causal observation window.

Input: components defaults to [], accepts at most 50 display-name substrings, and trims each 1–160 character string. roots defaults to [] and accepts at most 20 integer component IDs. budget is optional. fiberLimit defaults to 250, operationLimit to 20000, timeLimitMs to 8, targetOperationReserve to 4000, targetTimeReserveMs to 4, and adaptive to true; the numeric ranges are 50–20000, 1000–2000000, 1–500, 100–500000, and 0.5–250 respectively. lifecycle defaults to {bufferLimit:1000,targetReserve:100}; bufferLimit is an integer from 100 to 20000, targetReserve is an integer from 0 to 5000, and the reserve cannot exceed the buffer.

The expanded maxima are opt-in; defaults are unchanged. adaptive:true increases only later commit budgets after exhaustion and cannot restore evidence skipped by the exhausted commit. For a one-action window, especially on a large React Native tree, discard incomplete coverage and rerun the action from a fresh window with an explicitly larger budget.

npx @genie-react/cli call react_clear_renders \
  '{"components":["ProductCard"],"budget":{"fiberLimit":500,"timeLimitMs":12}}' --json

Output (selected fields):

{
  "ok": true,
  "tracking": true,
  "documentCommitId": 18,
  "observation": {
    "id": "observation:2",
    "epoch": 2,
    "startedAfterDocumentCommitId": 18
  },
  "observationConfig": {
    "adaptive": true,
    "fiberLimit": 500,
    "timeLimitMs": 12
  }
}

Agent use: Drive exactly one interaction next, then join render and effect evidence by observation.id. This call discards earlier counters and starts fresh retained histories, so capture needed evidence before clearing again. Use the subsequent render and cause evidence only when its coverage is complete and budgetExhaustedCommits is zero; adaptive recovery later in the window does not repair an earlier incomplete commit.

react_get_renders

Summarizes component render counts, changed inputs, timing, mount identity, and source.

Input: component is an optional component-name substring. sort is renders, updates, unnecessary, referenceOnly, unstable, or selfTime and defaults to renders. limit is an integer from 1 to 200 and defaults to 40. appOnly defaults to true. nameFilter accepts a case-insensitive whole-name glob (1–160 characters): * matches any text and ? matches one character. excludeNames accepts up to 50 such globs. minUpdates is a nonnegative integer and defaults to 0. These selectors combine with the existing component substring and run before source classification and the cap.

Use nextCursor with cursor to read the remainder; pass only cursor and optional limit on continuation calls. Pages retain the first report's ordering, counts, summary, source coverage, and observation metadata while new commits arrive. pagination reports the snapshot ID, row offset, total selected rows, and expiresAt in Unix milliseconds. omittedByLimit counts rows remaining after the current page, and nextCursor:null means that selected snapshot is exhausted.

Cursors expire after five minutes or eviction by newer reports (at most three paginated snapshots are retained). Clearing/restarting profiling or disposing tracking invalidates them. Reports with more than 5000 candidates matching the name/update filters (before appOnly) fail before snapshot construction, with a narrowing instruction. Invalid or expired cursors require a fresh call without cursor.

includeCursor defaults to true. Set it to false for a one-shot report: the requested row limit still applies, the summary covers all selected records, and omitted rows are not recoverable by cursor. No snapshot is retained, pagination.snapshotId and expiresAt are null, and the 5000-candidate snapshot limit does not apply. Background React quiet waits use this mode so polling cannot evict an active paginated report.

Source ownership is separate from pagination. If sourceClassification.complete is false, appOnly:true excludes unknown ownership and the result remains a lower bound even after its last page. Retry without cursor after source warmup to include newly classified rows.

Both react_get_renders and react_profile_report expose bundle (development, production, mixed, or unknown), timingsBundleDependent: true, and countsScope: "observed-run". The bundle describes registered React renderers in the app document. Timing values depend on that bundle and instrumentation. Counts describe this measured run, subject to coverage; they are not production estimates. Development Strict Mode behavior can differ from production. Compare runs with equivalent conditions and check collection availability even when the bundle is production.

sourceClassification reports app, library, and unknown ownership counts for matching records before appOnly and the result limit. totalCandidates counts that set; evaluated counts completed source lookups (including unresolved results and cache hits). complete is true only when every candidate has app or library evidence. Unknown ownership never counts as library.

Cold documents can exceed the source lookup budget. Genie warms the remaining sources in the background; retry the report to check recovery. If appOnly:true excludes unknown ownership, comparable is false with app-source-classification-incomplete, and summary semantics are lower-bound or unknown. appOnly:false keeps those records visible without inventing their ownership. If unknowns persist after relaunch, inspect this unfiltered report and restore source metadata or source-map access before comparing app-only measurements.

For const FriendListRow = memo(() => ...), the wrapper does not retain the variable's name. Genie uses an explicit wrapper displayName, then the inner function's name, and finally source information when available. To preserve anonymous memo binding names automatically, add the opt-in genie-react/babel plugin to your development Babel pipeline.

For Expo, add it to your existing Babel configuration (keep your other presets and plugins):

module.exports = function (api) {
  api.cache(true)
  return {
    presets: ['babel-preset-expo'],
    plugins: ['genie-react/babel'],
  }
}

For Vite 8 with @vitejs/plugin-react 6, install @rolldown/plugin-babel and @babel/core@^7 and add the naming plugin through that Babel adapter:

import babel from '@rolldown/plugin-babel'
import react from '@vitejs/plugin-react'
import genieNames from 'genie-react/babel'
import { genie } from 'genie-react/vite'
import { defineConfig } from 'vite'

export default defineConfig(({ command }) => ({
  plugins: [genie(), react(), command === 'serve' && babel({ plugins: [genieNames] })],
}))

Older @vitejs/plugin-react versions that expose babel.plugins can register 'genie-react/babel' there instead.

Restart Metro with --clear or restart Vite after changing Babel configuration. The plugin supports Babel 7 and only annotates inline anonymous arrows or function expressions passed to an import-proven React memo call, including import aliases and React.memo. It preserves named inner functions and explicit display names. Nested wrapper calls, assignment expressions, and components without a named variable declaration keep the existing runtime fallback. Babel production/test environments and Metro release callers receive no added metadata. The plugin changes debug names only; it does not enable render collection in release builds.

npx @genie-react/cli call react_get_renders \
  '{"component":"ProductCard","sort":"selfTime","limit":10,"appOnly":true}' --json

To select updated rows and exclude an internal wrapper before paging:

npx @genie-react/cli call react_get_renders \
  '{"nameFilter":"*Row*","excludeNames":["*Internal*"],"minUpdates":1,"limit":200}' --json

Pass the returned cursor to continue:

npx @genie-react/cli call react_get_renders \
  '{"cursor":"01234567-89ab-4cde-8fab-0123456789ab:200","limit":200}' --json

Output (selected fields):

{
  "tracking": true,
  "commits": 3,
  "documentCommitId": 21,
  "observation": {
    "id": "observation:2",
    "epoch": 2,
    "startedAfterDocumentCommitId": 18
  },
  "summary": { "totalRenders": 12, "totalUpdates": 8 },
  "components": [
    {
      "id": 51,
      "name": "ProductCard",
      "instance": {
        "mountId": "mount:9",
        "key": "sku-1",
        "logicalPath": "ProductList[key=sku-1] > ProductCard[key=sku-1]",
        "logicalIdentityEvidence": "keyed",
        "mountGeneration": 1
      },
      "renders": 4,
      "updates": 4,
      "selfTime": 1.2,
      "cumulativeSelfTime": 3.4,
      "causes": [
        {
          "kind": "state",
          "evidence": "exact",
          "name": "state[0]",
          "before": { "selected": false },
          "after": { "selected": true },
          "deepDiff": {
            "changes": [
              {
                "kind": "value",
                "path": "selected",
                "before": false,
                "after": true
              }
            ],
            "visited": 2,
            "truncated": false
          },
          "hook": { "index": 0, "stateIndex": 0, "kind": "state" }
        }
      ]
    }
  ],
  "omittedByLimit": 0,
  "coverage": { "complete": true, "inputAttributionComplete": true }
}

Agent use: Optimize the highest cumulative cost only after the exact changed path and complete coverage explain the render. Compatibility counts such as unnecessary are not proof that a render is safe to remove.

react_render_causes

Returns joinable render events and their observed causes.

For a single identified Query observer, Genie omits an inferred cause when its complete current notification policy excludes every changed subscribed field. notificationPolicyCheck.status is matched with changedSubscribedFields when a subscribed field changed, or unavailable with a reason when the policy, identity, or field comparison cannot be checked. The basis is current-effective-policy: this checks compatibility with current options, not historical notification delivery. Dynamic and private auto-tracked policies remain unavailable. Implicit error subscriptions from throwOnError are included; recorded exact deliveries take precedence and are never removed by this check.

Input: commit and afterCommit are optional nonnegative integers and are mutually exclusive. component is an optional substring. limit is an integer from 1 to 500 and defaults to 100. appOnly defaults to true.

npx @genie-react/cli call react_render_causes \
  '{"component":"ProductCard","afterCommit":1,"limit":20,"appOnly":true}' --json

Output (selected fields):

{
  "tracking": true,
  "documentCommitId": 21,
  "observation": {
    "id": "observation:2",
    "epoch": 2,
    "startedAfterDocumentCommitId": 18
  },
  "events": [
    {
      "renderEventId": "render:20:7",
      "observationId": "observation:2",
      "commitId": 2,
      "documentCommitId": 20,
      "componentId": 51,
      "componentName": "ProductCard",
      "instance": {
        "mountId": "mount:9",
        "key": "sku-1",
        "mountGeneration": 1,
        "logicalIdentityEvidence": "keyed"
      },
      "causes": [
        {
          "kind": "state",
          "evidence": "exact",
          "name": "state[0]",
          "before": false,
          "after": true,
          "deepDiff": {
            "changes": [
              { "kind": "value", "path": "", "before": false, "after": true }
            ],
            "visited": 1,
            "truncated": false
          },
          "hook": { "index": 0, "stateIndex": 0, "kind": "state" }
        }
      ],
      "sourceOwnership": "app"
    }
  ],
  "omittedByLimit": 0,
  "coverage": {
    "complete": true,
    "inputAttributionComplete": true,
    "droppedRenderEvents": 0
  },
  "renderEventRetention": {
    "evictedEvents": 0,
    "earliestDocumentCommitId": 19,
    "latestDocumentCommitId": 20
  }
}

Agent use: Join renderEventId, observation, document commit, mount, and hook IDs to the action, then change the producer of the exact input. Repeat the interaction if events were evicted or coverage is incomplete.

react_component_cohort

Reports lifecycle outcomes for every matching component instance in the current observation.

Input: component is a required non-empty string. exact defaults to true. limit is an integer from 1 to 200 and defaults to 50. appOnly defaults to false; set it to true to include only proven app-owned instances. Ownership includes mounted instances and cached unmount provenance. Unknown ownership is excluded by the app filter and lowers coverage. documentCommitId marks the captured traversal; attribution.status: "stale" means commits or a clear raced source lookup.

Each instance also reports its current renderingState, independently of recorded updates:

  • mounted-rendering: mounted and eligible to render; it may have stayed idle during the observation.
  • mounted-frozen: retained under a registered react-freeze boundary whose primary subtree has committed suspension.
  • mounted-hidden: retained under React Offscreen hiding, without proven react-freeze origin (for example, Activity).
  • mounted-unknown: the boundary state is unavailable.
  • unmounted: an actual unmount was observed during this observation.

Enable freeze identification in your development entry, alongside the Genie hook:

import 'genie-react/react-freeze'

The optional adapter imports and registers the actual Freeze export from react-freeze 1.x. It also identifies the same export used internally by react-native-screens when enableFreeze(true) suspends a screen. No component-name heuristic is used. If your workspace resolves multiple copies, register the app's public import too:

import { registerReactFreeze } from 'genie-react/react-freeze'
import { Freeze } from 'react-freeze'

registerReactFreeze(Freeze)

Use the same react-freeze package version/resolution as your navigation dependency. A copy that was not registered remains generic hidden evidence; the adapter never guesses its identity. coverage.freezeDetection reports whether the adapter is enabled. Frozen status requires committed suspension, so a delayed or pending freeze request does not count yet. Suspense fallback components remain eligible to render. Thawing preserves mount identity; actual unmounting produces a tombstone. None of these states proves effect or Query observer subscriptions.

Each instance reports reactVisibility independently of its lifecycle status:

  • hidden: a committed React Offscreen ancestor has hidden state, including Activity or suspended content.
  • not-hidden: no hidden React Offscreen ancestor was observed; this does not prove CSS or native screen visibility.
  • unknown: visibility cannot be established, including unmounted tombstones or incomplete boundary state.

A hidden instance remains in the mounted cohort with its mount identity. Revealing it preserves that identity; an observed full unmount produces an unmounted entry. React visibility does not prove which navigation or freeze mechanism hid the subtree. Activity can clean up effects and subscriptions while preserving state, so disappearing observers alone do not establish a full unmount. react-freeze uses Suspense, and generic hidden evidence cannot establish its origin.

npx @genie-react/cli call react_component_cohort \
  '{"component":"ProductCard","exact":true,"limit":100}' --json

Output (selected fields):

{
  "observation": {
    "id": "observation:2",
    "epoch": 2,
    "startedAfterDocumentCommitId": 18
  },
  "query": { "component": "ProductCard", "exact": true },
  "status": "updated",
  "matched": 1,
  "mountedUpdated": 1,
  "mountedIdle": 0,
  "mountedUnknown": 0,
  "unmounted": 0,
  "returned": 1,
  "omittedByLimit": 0,
  "instances": [
    {
      "componentName": "ProductCard",
      "status": "mounted-updated",
      "instance": {
        "mountId": "mount:9",
        "key": "sku-1",
        "logicalIdentityEvidence": "keyed",
        "mountGeneration": 1
      },
      "profileCommitId": 2,
      "documentCommitId": 20
    }
  ],
  "coverage": { "complete": true, "scanTruncated": false }
}

Agent use: Decide whether a list fix targets updated rows, unmounted rows, or identity churn. Read each returned instance's status even when global coverage.complete is false: mounted-updated and mounted-idle remain authoritative, as does an exact unmounted tombstone; mounted-unknown does not. The tool emits top-level absent only when root and lifecycle evidence is sufficient. Do not manufacture absence from matched: 0 when the status is unknown, or from omitted instances.

react_effect_audit

Ranks effect schedules and reports dependency, cleanup, hotness, and ownership evidence.

Input: component is an optional substring. onlyHot defaults to false. appOnly defaults to true. packageName is an optional trimmed string of 1–214 characters and requires appOnly:false. minUpdates is an integer from 1 to 1000 and defaults to 3. Legacy minFireRate is from 0.1 to 1 and defaults to 1. Preferred minScheduleRate is optional, is from 0.1 to 1, and takes precedence. limit is an integer from 1 to 200 and defaults to 40.

npx @genie-react/cli call react_effect_audit \
  '{"component":"Search","onlyHot":true,"minUpdates":5,"minScheduleRate":0.8,"limit":20}' --json

Output (selected fields):

{
  "tracking": true,
  "commits": 6,
  "documentCommitId": 24,
  "attribution": { "status": "current" },
  "hotnessCriteria": {
    "minUpdates": 5,
    "minScheduleRate": 0.8,
    "minFireRate": 0.8,
    "confidenceLevel": 0.95
  },
  "components": [
    {
      "id": 60,
      "name": "Search",
      "componentProvenance": {
        "ownership": "app",
        "evidence": "inferred",
        "reason": "nearest-symbolicated-fiber",
        "source": {
          "file": "/src/Search.tsx",
          "line": 12,
          "column": 1,
          "functionName": "Search"
        }
      },
      "effects": [
        {
          "index": 1,
          "kind": "effect",
          "depsMode": "list",
          "depCount": 1,
          "scheduled": 5,
          "updates": 5,
          "lastChangedDep": 0,
          "cleanupFunctionObserved": true,
          "provenance": {
            "ownership": "app",
            "evidence": "exact",
            "reason": "exact-hook-order",
            "hookCallsite": {
              "file": "/src/useSearch.ts",
              "line": 21,
              "column": 3,
              "functionName": "useSearch"
            },
            "hookDefinitionOwner": {
              "file": "/src/useSearch.ts",
              "line": 8,
              "column": 1,
              "functionName": "useSearch"
            },
            "package": null,
            "sourceMapConfidence": "mapped",
            "failureReason": null
          },
          "hotness": {
            "label": "hot",
            "samples": 5,
            "observedRate": 1,
            "minUpdates": 5,
            "minScheduleRate": 0.8,
            "confidenceInterval": { "level": 0.95, "lower": 0.57, "upper": 1 },
            "scheduleReason": "meets-threshold"
          }
        }
      ]
    }
  ],
  "omittedByLimit": 0,
  "effectsOmittedByLimit": 0,
  "coverage": { "complete": true, "inputAttributionComplete": true }
}

Agent use: Inspect the exact hook callsite and changed dependency before editing the effect. A schedule does not prove execution or cleanup timing, and unknown ownership must not be assigned to the app.

react_effect_timeline

Returns the preferred causal timeline for effect scheduling, execution, cleanup, and observed consequences.

Input: component is an optional substring. afterDocumentCommitId is an optional nonnegative integer. limit is an integer from 1 to 500 and defaults to 100. appOnly defaults to false. Set it to true to filter by the component that owns the scheduled effect. This ownership differs from the hook source reported by react_effect_audit. Read ownershipCoverage even when filtering leaves no events.

npx @genie-react/cli call react_effect_timeline \
  '{"component":"Search","afterDocumentCommitId":18,"limit":20}' --json

Output (selected fields):

{
  "tracking": false,
  "documentCommitId": 21,
  "observation": {
    "id": "observation:2",
    "epoch": 2,
    "startedAfterDocumentCommitId": 18
  },
  "events": [
    {
      "effectEventId": "effect:20:8",
      "effectId": "effect:mount:4:1",
      "observationId": "observation:2",
      "commitId": 2,
      "documentCommitId": 20,
      "componentId": 60,
      "componentName": "Search",
      "mountId": "mount:4",
      "effectIndex": 1,
      "kind": "effect",
      "phase": "update",
      "event": "scheduled",
      "evidence": "exact",
      "changedDependencySlots": [0],
      "changedDependencySlotsOmitted": 0,
      "dependencySlotsUnscanned": 0,
      "execution": {
        "status": "observed",
        "evidence": "exact",
        "startedAt": 1042.1,
        "completedAt": 1043.34,
        "durationMs": 1.24,
        "outcome": "completed"
      },
      "cleanupExecution": {
        "status": "unobserved",
        "reason": "no-cleanup-returned"
      },
      "consequences": {
        "status": "instrumented",
        "observedDomains": [
          "query-notification",
          "router-notification",
          "react-commit"
        ],
        "unobservedDomains": [
          "state-update",
          "external-store-write",
          "network",
          "event-listener",
          "navigation"
        ],
        "events": [
          {
            "kind": "notification",
            "domain": "query-notification",
            "notificationId": "query-notification:7",
            "timestamp": 1043.5,
            "evidence": "exact"
          },
          {
            "kind": "resulting-commit",
            "documentCommitId": 21,
            "timestamp": 1044.2,
            "evidence": "inferred",
            "reason": "next-commit-after-effect-execution"
          }
        ]
      },
      "timeline": [
        {
          "stage": "schedule",
          "timestamp": 1041.8,
          "evidence": "exact",
          "referenceId": "effect:20:8"
        },
        {
          "stage": "execution",
          "timestamp": 1042.1,
          "evidence": "exact",
          "outcome": "completed"
        },
        {
          "stage": "consequence",
          "timestamp": 1043.5,
          "evidence": "exact",
          "referenceId": "query-notification:7"
        },
        {
          "stage": "resulting-commit",
          "timestamp": 1044.2,
          "evidence": "inferred"
        }
      ]
    }
  ],
  "omittedByLimit": 0,
  "evictedEvents": 0,
  "droppedEvents": 0,
  "coverage": { "complete": true }
}

Agent use: Follow the exact notification ID to its consumer, but treat the next React commit as inferred and the listed unobserved domains as unknown. Repeat a clean observation when events were evicted or coverage is incomplete.

react_effect_events

Returns the same bounded effect causal timeline under the legacy event-oriented name.

Input: component is an optional substring. afterDocumentCommitId is an optional nonnegative integer. limit is an integer from 1 to 500 and defaults to 100. appOnly defaults to false and filters by the component that owns the scheduled effect. Read ownershipCoverage when interpreting an empty or partial result.

npx @genie-react/cli call react_effect_events \
  '{"component":"Search","afterDocumentCommitId":18,"limit":20}' --json

Output (selected fields):

{
  "tracking": true,
  "documentCommitId": 20,
  "events": [
    {
      "effectEventId": "effect:20:8",
      "effectId": "effect:mount:4:1",
      "observationId": "observation:2",
      "commitId": 2,
      "documentCommitId": 20,
      "componentName": "Search",
      "mountId": "mount:4",
      "effectIndex": 1,
      "kind": "effect",
      "phase": "update",
      "event": "scheduled",
      "evidence": "exact",
      "changedDependencySlots": [0],
      "changedDependencySlotsOmitted": 0,
      "dependencySlotsUnscanned": 0,
      "execution": { "status": "unobserved", "reason": "not-yet-observed" },
      "cleanupExecution": {
        "status": "unobserved",
        "reason": "not-yet-observed"
      },
      "consequences": {
        "status": "instrumented",
        "observedDomains": [
          "query-notification",
          "router-notification",
          "react-commit"
        ],
        "unobservedDomains": [
          "state-update",
          "external-store-write",
          "network",
          "event-listener",
          "navigation"
        ],
        "events": []
      },
      "timeline": [
        {
          "stage": "schedule",
          "timestamp": 1041.8,
          "evidence": "exact",
          "referenceId": "effect:20:8"
        }
      ]
    }
  ],
  "omittedByLimit": 0,
  "evictedEvents": 0,
  "droppedEvents": 0,
  "coverage": { "complete": true }
}

Agent use: Wait and read again when execution is not-yet-observed; do not infer that the effect ran from its schedule. Prefer react_effect_timeline in new workflows.

react_error_state

Reads caught errors, active Suspense boundaries, and a blank-tree hint.

Input: includeSource defaults to true. limit is an integer from 1 to 100 and defaults to 20. appOnly defaults to false. With true, ownership is resolved even when includeSource is false; that option omits source payloads while preserving ownership filtering.

npx @genie-react/cli call react_error_state \
  '{"includeSource":true,"limit":20}' --json

Output (selected fields):

{
  "caughtErrors": [
    {
      "boundaryId": 10,
      "boundaryName": "CheckoutErrorBoundary",
      "boundarySource": {
        "file": "/src/CheckoutErrorBoundary.tsx",
        "line": 7,
        "column": 1,
        "functionName": "CheckoutErrorBoundary"
      },
      "throwingComponent": "PaymentForm",
      "message": "Payment failed",
      "isLibraryBoundary": false,
      "forced": false
    }
  ],
  "suspended": [],
  "blankTreeHint": null
}

Agent use: Inspect the error, stack, throwing component, and boundary, then fix only the proven producer. If forced:true, reset overrides before diagnosing production behavior.

react_refresh_events

Reports Fast Refresh files, preserved instances, remounts, and excluded profile commits.

Input: afterSequence is an optional nonnegative integer. limit is an integer from 1 to 50 and defaults to 10. includeSource defaults to true. appOnly defaults to false. With true, ownership is resolved even when includeSource is false. Counters remain raw event totals; filtered component names require retained app ownership.

npx @genie-react/cli call react_refresh_events \
  '{"afterSequence":12,"limit":10,"includeSource":true}' --json

Output (selected fields):

{
  "events": [
    {
      "sequence": 13,
      "timestamp": 1752746705000,
      "filePaths": ["/src/Cart.tsx"],
      "updatedComponents": ["Cart"],
      "remountedComponents": [],
      "preservedState": [
        {
          "id": 31,
          "name": "Cart",
          "source": {
            "file": "/src/Cart.tsx",
            "line": 9,
            "column": 1,
            "functionName": "Cart"
          },
          "isLibrary": false
        }
      ],
      "counts": {
        "updatedComponents": 1,
        "remountedComponents": 0,
        "updatedFibers": 1,
        "remountedFibers": 0
      },
      "profileCommitsExcluded": 1,
      "truncated": false
    }
  ],
  "latestSequence": 13,
  "droppedEvents": 0,
  "partialSources": false
}

Agent use: Rerun the measured interaction when a refresh overlaps it, and investigate component identity when the edit remounted state that should have been preserved.

react_profile_start

Clears render counters and starts a profiling observation.

Input: components defaults to [], accepts at most 50 display-name substrings, and trims each 1–160 character string. roots defaults to [] and accepts at most 20 integer component IDs. budget is optional. fiberLimit defaults to 250, operationLimit to 20000, timeLimitMs to 8, targetOperationReserve to 4000, targetTimeReserveMs to 4, and adaptive to true; the numeric ranges are 50–20000, 1000–2000000, 1–500, 100–500000, and 0.5–250 respectively. lifecycle defaults to {bufferLimit:1000,targetReserve:100}; bufferLimit is an integer from 100 to 20000, targetReserve is an integer from 0 to 5000, and the reserve cannot exceed the buffer.

The expanded maxima are opt-in; defaults are unchanged. adaptive:true increases only later commit budgets after exhaustion and cannot restore evidence skipped by the exhausted commit. For a one-action profile, especially on a large React Native tree, discard incomplete coverage and rerun the flow from a fresh profile with an explicitly larger budget.

npx @genie-react/cli call react_profile_start \
  '{"components":["SearchResults"],"lifecycle":{"bufferLimit":2000,"targetReserve":200}}' --json

Output (selected fields):

{
  "ok": true,
  "tracking": true,
  "documentCommitId": 24,
  "observation": {
    "id": "observation:3",
    "epoch": 3,
    "startedAfterDocumentCommitId": 24
  },
  "observationConfig": {
    "lifecycleBufferLimit": 2000,
    "lifecycleTargetReserve": 200
  }
}

Agent use: Run the exact flow to measure next, then stop before reading the final report. Starting clears current counters but keeps named snapshots for later diffs. Compare the result only when coverage is complete and budgetExhaustedCommits is zero.

react_profile_stop

Stops profiling while retaining aggregates and named snapshots.

Input: {}; this tool has no input fields.

npx @genie-react/cli call react_profile_stop '{}' --json

Output (selected fields):

{ "ok": true, "tracking": false, "commits": 5 }

Agent use: Read reports from the frozen five-commit window before another start or clear changes the counters. Stopping does not uninstall instrumentation or delete retained snapshots.

react_profile_snapshot

Stores current app-component aggregates as a named diff baseline.

Input: label is a string and defaults to baseline; reusing a label replaces its snapshot. appOnly defaults to true. Use the same value for the later react_renders_diff call. If app filtering excludes unknown ownership, the baseline remains a lower bound; warming source metadata later cannot repair this stored baseline.

npx @genie-react/cli call react_profile_snapshot \
  '{"label":"search-before"}' --json

Output (selected fields):

{
  "ok": true,
  "label": "search-before",
  "commits": 5,
  "components": 18,
  "coverage": { "complete": true, "inputAttributionComplete": true }
}

Agent use: Keep this baseline only if coverage is complete, then run the same flow after the change and compare it by label. Reuse the label to replace stale retained data.

react_renders_diff

Compares current render aggregates with a named profile snapshot.

Input: baseline is a string and defaults to baseline. thresholdMs is a number with no contract range and defaults to 0.5. appOnly defaults to true and must match the stored baseline. Read both sides' coverage, including sourceClassification, before comparing totals.

npx @genie-react/cli call react_renders_diff \
  '{"baseline":"search-before","thresholdMs":0.5}' --json

Output (selected fields):

{
  "baseline": "search-before",
  "commits": { "before": 5, "after": 4 },
  "clearsSinceBaseline": 1,
  "coverage": {
    "baseline": { "complete": true },
    "current": { "complete": true }
  },
  "selfTimeMs": { "before": 14.2, "after": 8.1, "delta": -6.1, "pct": -43 },
  "regressed": [],
  "improved": [
    {
      "name": "SearchResults",
      "source": "/src/SearchResults.tsx:11",
      "deltaMs": -5.4,
      "before": { "renders": 5, "selfTime": 11.7 },
      "after": { "renders": 4, "selfTime": 6.3 }
    }
  ],
  "added": [],
  "removed": []
}

Agent use: Accept the change only when equivalent flows and complete coverage show the intended component improved without regressions. When counters were cleared, removed means not observed in the current window, not deleted from source.

react_profile_report

Returns bounded render-cost and render-count leaderboards for the current profile.

Input: limit is an integer from 1 to 100 and defaults to 20. component is an optional display-name substring and filters before the leaderboard limit. appOnly defaults to false; set it to true for proven app-owned components. Unknown ownership excluded by that filter lowers coverage.

npx @genie-react/cli call react_profile_report \
  '{"limit":10}' --json

Output (selected fields):

{
  "commits": 4,
  "tracking": false,
  "documentCommitId": 28,
  "attribution": { "status": "current" },
  "coverage": { "complete": true, "inputAttributionComplete": true },
  "slowest": [
    { "id": 51, "name": "ProductCard", "selfTime": 2.1, "renders": 4 }
  ],
  "mostRerendered": [
    { "id": 51, "name": "ProductCard", "renders": 4, "unnecessary": 0 }
  ],
  "mostReferenceOnly": []
}

Agent use: Use the slowest or most-rendered row as a shortlist, then inspect its exact render causes before editing. Causal leaderboards are not sufficient when input attribution is incomplete.

react_measure

Opens a labelled measurement without clearing global counters.

Input: label is required, trimmed, and 1–160 characters. The response includes handle, label, startedAfterDocumentCommitId, and expiresAt (Unix milliseconds). Collection must be active and able to observe commits.

npx @genie-react/cli call react_measure '{"label":"open checkout"}' --json

Output (selected fields):

{"handle":"span-id","label":"open checkout","startedAfterDocumentCommitId":18,"ownership":"newest-open-span","attribution":"temporal-only"}

Agent use: Drive the UI next, then read and close the returned handle. Ownership is explicitly newest-open-span: while multiple spans are open, only the newest records commits. Closing it resumes the previous span. This gives disjoint commit sets, not causal isolation; background work can still be included. At most 20 spans are retained for five minutes. A closed span may be evicted at capacity; opening fails if all 20 are open. Reload/Fast Refresh invalidates handles.

react_renders_since

Reads the labelled span's owned commits.

Input: handle is required (1–100 characters), close defaults to false, component optionally filters by name substring, appOnly defaults to true, and limit is an integer from 1 to 200 (default 40). Set close:true after driving the interaction and waiting for commits to settle. Re-reading a closed span preserves its captured counts.

npx @genie-react/cli call react_renders_since '{"handle":"<returned handle>","close":true}' --json

Output (selected fields):

{"label":"open checkout","commitIds":[19,21],"commits":2,"excludedCommits":1,"coverage":{"complete":true,"semantics":"exact"}}

Agent use: Compare labelled counts without clearing another span's evidence. The response carries label, disjoint commitIds, excludedCommits owned by newer spans, components, summary, sourceClassification, and coverage. Counts are frozen before source lookup. omittedByLimit identifies rows outside the selected page; the summary covers all selected components. Global clear does not erase span evidence. A span retains at most 500 components and 1000 commits; hitting the commit cap closes it. Collection and retention losses make coverage incomplete. Unresolved source ownership also makes an app-only report incomplete; use appOnly:false to inspect all retained records. attribution:"temporal-only" explicitly means these counts do not prove which interaction caused a commit.

react_quiesce

Waits until React document commits have remained unchanged for the requested interval.

Input: idleMs is an integer from 100 to 5000 (default 500); timeoutMs is an integer from 1 to 60000 (default 10000). Unknown fields are rejected. The CLI transport allows the requested deadline.

npx @genie-react/cli call react_quiesce '{"idleMs":500,"timeoutMs":10000}' --json

Output (selected fields):

{"ok":true,"outcome":"idle","elapsedMs":750,"observedCommits":3,"documentCommitId":21,"renderCollection":"available"}

Agent use: Clear, drive one UI interaction, quiesce, then read the render report. Outcomes are idle, timed-out, or unavailable; observedCommits counts increases between valid samples, excluding commits before the first sample. A missing collector, changed document, or counter reset cannot prove idle. Historical late-hook degradation remains visible but can establish a new quiet interval. Failed samples restart the quiet interval; silent requests respect the overall deadline. Tool-catalog refreshes in the same document preserve the wait. Add --fail-on-result-error for a nonzero exit on a result with ok:false. This observes React commits, not continuous canvas/native frames, query freshness, or future scheduled work.

On this page