Skip to content

Releases

1.0.0

Choose a tag to compare

@adityareddy-dev adityareddy-dev released this 15 Oct 00:20

Changed

  • 1.0.0 keeps 0.26.0's runtime and starts the API promise in the README. Existing report fields keep their meanings
    through 1.x, and the documented experimental and debugging exceptions still apply.
  • The bar I set for 1.0, at least 38 right and at most 6 wrong on 62 verdicts from apps it hadn't seen, isn't met.
    0.25.0 was right on 8 of 62, with 35 wrong and 19 misleading, and no later version has been graded on fresh
    apps. The accuracy page
    has the count.

Blame changes

None from 0.26.0.

Sources

0.26.0

Choose a tag to compare

@adityareddy-dev adityareddy-dev released this 15 Oct 00:20

Changed

  • Behaviour change: the note on a later render INP didn't time no longer says "people still wait for it". It now
    reads "INP doesn't count it, and what started it can't be told." Nothing ties that render to the interaction but
    when it landed, and on hyperdx it was more than once a live tail's timer or a route change, not the click. The
    render itself is still reported with its count and its time, and a render INP did time is said as before.
  • Every render inside the script the screen update waited on is counted there, not only the ones with work in them.
    On kaneo a menu's FrameRequestCallback held 8 renders and the sentence said 4, then set the other 4 apart as
    renders after the handlers, "13 ms of rendering in all across 5 commits".
  • A later render is said to force layout only where its script held no other commit. A long animation frame gives
    one forced layout figure per script, and on hyperdx the later render was given its whole task's 12 ms where a
    trace put 1.5 ms of it on that render. Where the script held another commit too, the layout clause is left out.
  • A later render whose walk ran out of budget is said by its count alone, "rendering at least 4983 components",
    not "inside DBSearchPageContent". The name was where the walk stopped, not where the render went.
  • On a production build a render of 1000 components or more takes the blame without a long task of working time,
    where the working time was the larger part of the interaction. hyperdx re-rendered 3104 components in 38 ms of a
    40 ms click and the sentence said the rest went to waiting and painting. A shadcn/ui Sheet's 59 components and
    excalidraw's 149 stay under the line, and a screen update longer than the working time keeps the verdict.

Sources

0.25.0

Choose a tag to compare

@adityareddy-dev adityareddy-dev released this 15 Oct 00:20

Changed

  • A layout forced in a commit with several roots is named after the component they all sit under, with the whole
    commit's count. A Radix dialog mounts its overlay and its content as two portals under DialogPortal, so on plate
    the search dialog's opening was blamed on CommandGroup, the end of the path through the content, where the read
    that forced the layout can be in either. It now reads "rendering 89 components inside DialogPortal", and the
    devtools entry names DialogPortal too. Where the roots sit under no component it names the app. hotPath is
    unchanged, and a report stored by an earlier release is named as before.
  • "React rendered N times before the screen updated" counts every commit that rendered a component on a production
    build too, as it has on a development build since 0.19.0. Before, a production build counted only commits of 10
    components or more, so plate's search dialog read "React rendered 8 times" where 13 commits rendered. The note is
    still said only where two of those renders had work in them, and the Performance panel's Summary count moves with it.
  • A render is said to be mounting only where every component it rendered mounted. 0.24.0 drew the line at nine in
    ten, so on plate the render after the search dialog opened read "75 ms mounting 1959 components" where 1838 mounted
    and 121 re-rendered, and that reads as 1959 mounts. The count was right and the verb wasn't. It now reads
    "rendering 1959 components", the dialog's own mount of 89 with 87 new reads "rendering 89 components" too, and the
    badge panel's rows say "rendered". A render where every component mounted still reads "mounting".
  • A forced layout is said to overlap React's render only where the render inside the time it was counted across is
    more than what the layout left of it. Before, the render set against that time took in commits between one event's
    handlers and the next's, which the time leaves out, so on plate's search dialog 62 ms of rendering, 45 of it between
    the pointerdown's handlers and the pointerup's, was set against the 28 ms left and the cause said the layout
    overlapped React's render. The click's own renders were 17 ms. It now says what was left, and where React's total
    is more than that, how much of it came between the handlers: "Of the 62 ms, 45 ms came between the pointerdown's
    handlers and the pointerup's, outside the 168 ms."
  • A later render is said to have made the browser recalculate styles and layout only for what the scripts it
    committed or ran its effects in forced. Before, it took every script in its frames, so on plate's search dialog in
    development a dev overlay's animation frame callback, which forced 7 ms (45 ms at 4x CPU throttling) after React's
    task had forced none, was put on the render. That clause is gone there.

Sources

0.24.0

Choose a tag to compare

@adityareddy-dev adityareddy-dev released this 15 Oct 00:20

Changed

  • A component that is all its parent rendered and hands the very children it was given to the one component it
    renders, which carries most of its work, is no longer the one a render or layout blame names.
    Before, the walk
    went into it like any other, so on Cap a sidebar component that keeps its popover's open state and wraps its
    whole return in Radix's Dialog root was blamed as 'Dialog', '211 of 212 components', where the code to change is
    the sidebar component. Radix's root hands its children to DialogProvider as it got them. Now such a component
    spends no step on the hot path and is never the one named, where a component above it can be. Where something
    below it carries most of the work the path goes on to that as before, and where nothing does it ends at the
    nearest component above it a reader could search for, which the blame then names. hotPath in a report,
    web-vitals' react.hotPath and OpenTelemetry's react_inp_blame.hot_path end there too. Having children isn't
    enough: a feed given its empty state, a form given its submit row, a highlighter given its code and a table given
    a toolbar build what they render themselves, and are named as before.
  • Where React timed the render (a development or profiling build), a component of that shape whose own render took
    the time, a permission check round a fade or a highlighter handing on the string it was given, is named as in
    0.23.0. What the rule still can't tell: a production build has only counts, and there such a component is passed
    over all the same. That moves the name only where nothing below it is named, a dialog root whose own render was
    slow say, which is then blamed on the component above it. A permission check round a fade or a highlighter
    handing on its string gets the name 0.23.0 gave. A wrapper that wraps its children in an element of its own before handing
    them on is named as in 0.23.0, and where one of those (a Card, a layout) sits between the component that owns the
    state and a dialog root, the render is named after that one rather than the owner. On Cap, over six runs each of
    a development and a production build at full speed and at a quarter of it, every report that blamed a render on
    the sidebar's collapse, its popover's opening or the popover's close by Escape named AdminNavItems. The rule
    changed no name on the demo apps.
  • A render is said to be mounting only where nine in ten of its components rendered for the first time, and
    re-rendering only where one in ten or fewer did.
    Before, it went by half, so on formbricks a render that
    mounted 134 of 217 components read "mounting 217 components", and on Cap 160 components a Tooltip remounted among
    332 read "re-rendering 332 components". Between the two it now reads "rendering", and the badge panel's rows say
    "rendered".
  • /auto grew 0.05 KB and the part before react-dom 0.04 KB, both still within their budgets. The rest stay as
    they were.

Blame changes

  • A render or layout blame whose path went into a component that was all its parent rendered and handed the very
    children it was given to the one component it rendered, which carried most of its work, with nothing named below
    it and a component above it that can be: name goes from that component to the nearest one above it
    ('AdminNavItems' where 0.23.0 gave 'Dialog'), and a count in detail is that one's.
  • A render where more than one in ten and fewer than nine in ten of the components mounted: the cause's verb goes
    from "mounting" or "re-rendering" to "rendering". name, detail and ms don't change.

Sources

0.23.0

Choose a tag to compare

@adityareddy-dev adityareddy-dev released this 15 Oct 00:19

Changed

  • A render or layout blame whose commit's path ends in a component the walk found no name for is named 'the
    app'.
    Before, a root written as an anonymous function, with no child holding most of what it rendered, gave a
    blame named '(anonymous)', and a render where such a component was half the commit was "mostly (anonymous)",
    with '(anonymous) ×300' as its detail, or "(anonymous)'s own render" where most of its time was its own. Now the
    blame reads 'the app', the fixed value for a walk that gives no component to name, and the detail is the
    commit's count, with no part of it said to be inside the app. The sentence no longer says where the render started
    isn't known where only the name was missing, and a production walk cut at walkBudget whose first root has no
    name still says it.
  • A layout forced in a script several React commits ran in is named after the commit whose time could hold
    it.
    Before, the blame took the subtree of the commit that rendered most, so a menu opening in a production
    build, with four commits in one listener and 44 ms of forced layout, was named after a 295-component list whose
    effects took 0.1 ms rather than the 185-component menu whose layout effects measured its items. Now each commit
    there is weighed by React's own time for it (render, committing and effects) where the build times renders, and
    otherwise by its effects plus the time since the commit before it in the same script, which for the first one
    runs from the script's start or the handlers', whichever is later, and so holds the handler too. The largest
    names the layout where it could hold at least half of it. Where none could, name and detail are null and the
    cause says React committed that many times there and none took long enough to hold most of it. One commit in the
    forcing script is named as before. The cause describes the commit the blame names, so a Sheet closing inside the
    forcing script beside a 500-row Table rendered later reads Dialog with '56 components' again, and the sentence
    says the Sheet's render rather than the Table's.
  • The Performance panel's entry, the badge panel's component list and web-vitals' hotPath and components
    follow the commit a layout or render blame names.
    Before, all three took the heaviest commit, which could be a
    render the blame doesn't name, such as a key press's larger render beside a blame on its release's own. Now a
    layout or render blame's commit is the one it names, where its name and detail tell which commit that is, and the
    Performance panel's Heaviest path row still shows the heaviest. For attributeINP, react.hotPath and
    react.components on a layout or render blame now come from that commit, and so does OpenTelemetry's
    react_inp_blame.hot_path, which is read from them. The react object stays at schemaVersion: 1.
  • A render blame names the commit whose committing and effects are worth saying beside its render, and says
    them.
    Before, in a build that times renders, a commit's committing and effects counted towards naming it only
    where they were worth a mention across all the commits, so a Toolbar that rendered for 7.5 ms and then spent 8
    more committing and running useEffect callbacks lost to a Nav that rendered for 14.2 ms with 2 more. The sentence
    gave the render alone, so most of blame.ms could go unsaid. Now committing and effects that reach 5 ms and a
    fifth of a commit's time count for it, and once one commit's do, any other commit's from 5 ms count for that one
    too, so a larger render never loses the name for being larger. The sentence says each of them from 1 ms. Where
    some of the commit's time would still go unsaid, it adds "That commit took 91 ms with committing and effects",
    and beside the named commit's figures it gives what the other commits spent rather than the totals across all
    of them.
  • A render blame whose hot path ends in a component React's times show took under half of the render is named
    where the render started.
    Before, a render of 386 components from Shell down was named after PopupContent, at
    the end of a path through a dropdown's layers, though the times on that path showed PopupContent held at most 73
    of its 165 ms. Now each name above the end holds it, so the smallest of their totals bounds it, and where that is
    under half the render and the render started from one component that holds the whole commit, the blame is named
    after Shell with the whole commit's count. The sentence still says how many were inside PopupContent. A build
    with no durations keeps the deepest name, and the Performance panel's entry takes the start name too.
  • A production render held by the handler's script that a long animation frame measured is blamed under 50 ms
    of working time.
    Before, a count under a long task of working time was never a slow render, so an Enter press
    with 49 ms of working time, whose onKeyDown script a long animation frame measured at 32 ms and which
    re-rendered 386 components, read as nothing that stood out. Now, where a frame
    measured the handler's own script holding the commit at 20 ms or more, and not mostly forced layout, the count
    names that commit's render, and the sentence gives the script's measured time in place of the working time. A
    screen update that outran such a render says it the same way in its note. Without a frame, or under a script that
    doesn't hold the commit, it reads as before.
  • A key release that waited for its press's screen update is blamed on that wait, under 50 ms too. Before, a
    40 ms release that waited 22 ms for the menu its press had just opened to paint read as nothing that stood out.
    Now, where the press's handlers had ended when the key came up and its paint came only after the release's
    handlers began, and the wait is the largest phase and at least half the interaction, the blame is waiting, with
    the input delay as its ms and no name. The sentence names the press's render where it carried work.
  • A render a key's press committed just before its release is the press's, where the press painted on its own.
    Before, a commit stamped with the press, landing a moment before a slower release, counted in the release's
    working time, its render count and its "React rendered N times" note, and could be the render the release was
    blamed on. Now it is left out of all three, and of the Performance panel's counts, though it stays in commits
    and on the panel's renders track. A press painted together with its release still counts.
  • A screen update that outran a production render short of a long task says that render where the working time
    was at least half of the update.
    Before, the count was said only where the working time reached 50 ms, so the
    same 400-row render showed under a 76 ms screen update at 50 ms of working time and not at 49. Now it shows at
    both, wherever the working time is half the screen update or more.
  • The browser's own work after the handlers reads "recalculating styles and layout". It read "recalculating
    styles and layout for what changed", which pointed at the interaction's own changes where a restyle a write to
    body or html set off is as likely. Known limits says that restyle can't be named.
  • The docs define a render blame's ms. The API page, the Blame.ms JSDoc and the OpenTelemetry page now
    say it is the named commit's render, plus committing it, plus its useEffect callbacks in the same task, with no
    other commit in it except a render committed inside those callbacks that can't be taken out, which the sentence
    says. So it can be more or less than commits.ms.
  • /auto is 35.5 KB gzipped, up from 34.4 KB, react-inp-blame/otel is 1.6 KB, up from 1.5 KB, and
    react-inp-blame/web-vitals is 0.9 KB, up from 0.8 KB.
    Of
    /auto's 1.1 KB, naming a layout across several commits took 0.36 KB, a release's wait for its press 0.18 KB,
    committing and effects choosing a render 0.14 KB, the measured handler script 0.13 KB and naming a render where
    it started 0.10 KB. Naming the app for a component with no name took 0.07 KB, the measured script's note 0.06
    KB, leaving the press's render out of the release 0.05 KB, and the screen update's note short of a long task
    0.03 KB. otel grew 0.03 KB and react-inp-blame/web-vitals 0.02 KB, from web-vitals' attribution taking the
    commit a layout or render blame names, which otel carries. The part before react-dom stays at 11.6 KB and the
    badge and panel at 7.1 KB.

Blame changes

  • A render or layout blame whose commit's path ends in a component the walk found no name for, with no other name
    on it to take: name goes from '(anonymous)' to 'the app'.
  • A render blame where a component the walk found no name for was half the commit, or half its time in its own
    render: detail goes from '(anonymous) ×N' or "(anonymous)'s own render" to the commit's count.
  • A render or layout blame named 'the app' for a path ending in a component with no name, where the walk counted
    fewer components inside that one than in the commit: detail goes from 'M of N components' to 'N components'.
  • A layout forced in a script several commits ran in: name and detail go from the commit that rendered most to
    the one whose time could hold most of the layout, or to null where none could hold half of it.
  • A layout forced in a script one commit ran in, beside a larger render in a later script: name and detail go
    back to where that commit's render started, with the whole commit's count ('Dialog', '56 components' where 0.20.0
    to 0.22.0 gave 'DismissableLayer', '15 of 56 components').
  • A render blame in a build that times renders, where a commit's committing and effects reach 5 ms and a fifth of
    its time: that commit can now be the one named over one that rendered longer but took less time in all, with
    each commit's committing and effects counted wher...
Read more

0.22.0

Choose a tag to compare

@adityareddy-dev adityareddy-dev released this 03 Oct 02:30

Changed

  • A render the walk could not tell where it started is named 'the app' in Chrome's Performance panel, as the blame sentence names it. Before, a production render cut at walkBudget with no component to name read 'React render · root (at least 5000 components, time not measured)' on its track while the blame said 'the app'. Now the track reads 'React render · the app (at least 5000 components, time not measured)', and a hydration or a later render reads the same way.

No size moved, /auto stays 34.4 KB gzipped.

npm install react-inp-blame@0.22.0

0.21.0

Choose a tag to compare

@adityareddy-dev adityareddy-dev released this 02 Oct 03:19

Added

  • Each commit says where its hot path starts, in pathStart. Before, a reader could not tell a path that
    starts where the render began from one that starts at a component the roots only sit under. Now every commit in
    commits and followUps carries 'only-root', 'heaviest-root', 'unknown-root' or 'no-root', the last
    exactly where nothing rendered. It is absent only on a report from before 0.21.0, and schemaVersion stays 4.

Changed

  • In a production build a walk cut at walkBudget names the app where it can't tell where the render started,
    and says so.
    Before, a render past the budget could be named after a component the subtrees only sit under, as
    if the render came from it, with only a note that the count was partial. Now the blame names the component the
    render started at only where one rendered at the top and holds everything the walk reached with nothing it didn't
    reach rendered beside it, and otherwise 'the app'. The sentence says the walk stopped partway through, so which
    components took the time isn't known, or where it started and which components took the time aren't known. A
    development or profiling build that could tell where the render started keeps the note that the component count
    is partial.
  • In a development build a walk cut at walkBudget before it reaches a second root names the app where React's
    total shows the part it missed took longer.
    Before, it named the one subtree it reached, with that subtree's
    time only. Now it reads 'the app' with React's figure for the whole render, and the sentence says where it
    started and which components took the time aren't known. A commit's total is React's own figure for the render
    on any walk cut at the budget on a root React timed. Where the root it reached took longer than the part it
    missed, the blame keeps that root's name, and the figure is React's total for the whole render, which can include
    the root it missed. Before, it gave the root's own time and called the rest committing. A root timed only under
    <Profiler> keeps the sum of what was walked.
  • A render or hydration blame on a walk cut short says the rest was not walked. Its detail read 'at least 1998
    of at least 5000 components' and now reads 'at least 1998 of at least 5000 components, the rest not walked'.
    Where the useEffect callbacks lead, a cut walk no longer names the one mounted component it happened to reach,
    and the detail reads 'useEffect callbacks in at least 1 mounted component'.
  • A navigation type the browser adds later reads as navigationType: 'navigate'. The document's own load took
    its type straight from the browser's navigation entry, so a type a browser added later would have reached a
    report outside the documented set (a some_new_type showed as 'some-new-type'). Now navigate, reload,
    back_forward and prerender map as before, and anything else reads as 'navigate', so code switching on
    navigationType never meets a value missing from the list. Since 0.1.0.
  • What 1.x promises now says when the security backport and the Node floor apply. SECURITY.md and the README
    said security fixes go into the latest minor and the one before it for 90 days, which read as already in force.
    Now both say it starts at 1.0.0, and until then every fix goes into the latest release only. The README also said
    the build plugins run on Node 20.19 and later through all of 1.x. Now 20.19 is the floor at 1.0.0, a minor can
    drop a Node line once it is past its end of life upstream, and the CHANGELOG says so under Changed, never in a
    patch.
  • The API page lists every fixed set of strings a report and stats() hold, each frozen for 1.x. Before, the
    README gave two examples (blame.kind, stats().mode) and no list. Now docs/api.md names each set with its
    values, including hydratedTarget.scope and the react object attributeINP adds, and says a new value comes
    only in 2.0.0. It also says pointerType is the browser's own string (an empty one reads as null), that 'the app' is the one fixed value of blame.name, taken by a render or layout blame whose commit's walk named no
    component, and that announceNavigation for a change of query or hash alone still starts inp() over. The
    README's Versions sentence links to the list.
  • /auto is 34.4 KB gzipped, up from 34.1 KB, and the part of it that runs before react-dom is 11.6 KB, up from
    11.5 KB.
    Nearly all of /auto's 0.28 KB went on the walk cut at walkBudget: 0.22 KB on recording where its
    path starts and naming the app where it can't tell, and 0.04 KB on the development build's check against React's
    total for the render. The navigation type mapping added 0.02 KB, and the other fixes came to about nothing
    between them. The part before react-dom grew 0.09 KB, all of it the same walk: 0.04 KB for where the path starts
    and 0.05 KB for the development build's check. The badge and panel stay at 7.1 KB, react-inp-blame/otel at 1.5
    KB and react-inp-blame/web-vitals at 0.8 KB.

Fixed

  • The panel row no longer says a root updated where the walk couldn't tell the render started there. A
    production render past the budget read 'App updated · at least 5000 components mounted inside the app' and now
    reads 'at least 5000 components mounted inside the app'.
  • A press whose pointer was not seen reads as a click in its headline, cause and verdict, as its panel row
    already did.
    Before, the verdict said '120 ms tap' while the row said click, for a press that could just as
    well have been a mouse's. Now only a finger's or a pen's press is a tap.
  • inpBlameAttributes gives an INP metric whose entries getter throws the library-error status. Before, such
    a metric got an empty object, so the INP event went out with no react_inp_blame.status at all. Now a metric
    named INP counts as INP before its entries are read, and the throw is reported as library-error, or
    not-installed where the library isn't on the page. Since 0.20.0.
  • The production either-or cause gives the component count once. Before, it read "The onClick handler or
    React's render of StatsPanel (2 components) most likely took the 135 ms, the handler the likelier: React
    re-rendered only 2 components." The count is now only in the reason the handler is the likelier. Since 0.20.0.
  • A panel row whose useEffect detail names the component it blames leaves out 're-rendered'. 'SalesChart
    re-rendered · useEffect callbacks after mounting SalesChart' read as a contradiction, and now reads 'SalesChart ·
    useEffect callbacks after mounting SalesChart'.
  • A click that changed only the query or the hash says it started a navigation within the page. A report keeps
    only a URL's origin and path, so a query-routed step from ?step=1 to ?step=2 read "It started a navigation to
    /checkout." about the page the click was already on. Now it reads "It started a navigation within /checkout."
    Since 0.20.0.
  • startedNavigation.type keeps to its fixed set. The Next.js App Router integration passed the router's own
    navigation type straight through, so a type other than push, replace or traverse (a refresh, say) landed in the
    report as it was. It now reads as 'push', as announceNavigation's always has. In 0.20.0 and before.

Security

  • SECURITY.md says exactly what clear() and dispose() drop. It said retained data lasts until dispose(),
    clear() or unload. clear() drops only the commits, reports, quick interactions and Event Timing entries, and
    dispose() drops the commits and the inputs with their elements, while the reports, frames and navigations stay
    reachable through the object install() returned until the page lets go of it. It now says so.

0.20.0

Choose a tag to compare

@adityareddy-dev adityareddy-dev released this 02 Oct 00:25

Added

  • Any router can tell the library a route changed, with announceNavigation(url) from the package root.
    Before, only the Next.js App Router's navigations started inp() and the badge over and moved navigationURL,
    so in a React Router or TanStack Router app every report stayed on the first URL. Now the reports after a call
    carry the new URL with navigationType: 'soft-navigation', and a call made inside a click names that click in
    startedNavigation. The install docs show where React Router and TanStack Start apps call it. React Router gets
    a small AnnounceNavigations component, rendered before the Outlet, that announces only when the URL changed,
    so a link to the page it's already on doesn't start inp() over. TanStack Start announces from inside the click,
    so the click is named on its report, and compares with the URL it last announced, so going back before a slow
    loader finished, or a redirect to the page it was on, is announced too. It waits for the first load, so a route
    whose validateSearch fills in a default the URL lacks isn't announced on landing, and it is for browser
    history. CI builds both apps from these lines and checks where each report is placed. Under React Router, a quick
    link click whose route renders in a later task gets no report, which Known limits says.
  • A report says which build of react-dom measured it, and the badge marks a development build dev. Reports
    had no way to tell a development number from a production one, and the badge looked the same, though React's
    development build is slower and StrictMode renders twice there (one Vite app read 324 ms of React render with
    StrictMode and 165 ms without). Each report now carries reactBuild, 'development', 'production' or 'profiling',
    and strictMode, true where a component that rendered was under <StrictMode> and null outside a development
    build or where the report holds no render. The web-vitals react field carries both. On a page with more than
    one react-dom, a development one makes the report 'development' in whatever order they registered, including one
    this library cannot read. Under a development react-dom the badge carries a small 'dev' mark from before the
    first interaction, and the panel head says to check anything amber or red in production, with a link to the
    install guide. The Next.js and Vite installs each have a short section on it, both READMEs say development
    numbers run high, and the recipes label reports by reactBuild before forwarding them. No number changes, and
    the rating and its colour stay as measured.
  • react-inp-blame/otel, experimental, puts the blame on the INP event an OpenTelemetry setup already sends.
    It adds which component or handler made an interaction slow to the INP event OpenTelemetry's web vitals
    instrumentation, Honeycomb, Elastic, Embrace or Grafana Faro already sends, as react_inp_blame.* attributes:
    InpBlameLogRecordProcessor for a log record, inpBlameAttributes() for a hook. Every INP record gets
    react_inp_blame.status (matched, not-installed, sampled-out, no-report or library-error), so a
    production build with nothing installed says not-installed instead of sending nothing. The setups, what lands
    on the record and the limits are in docs/opentelemetry.md. The entry, its exports and every attribute name are
    outside the version promise until OpenTelemetry names these fields.

Changed

  • A report's navigationURL and startedNavigation.url keep only the URL's origin and path, and schemaVersion
    is 4.
    They held the whole URL as web-vitals does, query string, fragment and any user:password@ included, so
    a token or an email in a query went out with every forwarded report. Now
    https://ann:s3cret@shop.example/products?token=abc#x reads https://shop.example/products, whichever router,
    announceNavigation() or copy of the library announced it. A URL that does not parse is cut at its first ? or
    #. web-vitals' navigationURL still keeps the query, so match a report with web-vitals' INP on origin and
    path. A HashRouter or query-routed app loses the route detail it had there. The react object attributeINP
    adds stays at schemaVersion: 3, since it carries no URL. Since 0.1.0.
  • blame.detail is display text, like the headline. It was listed with the data, so a dashboard could group on
    strings like '15 of 56 components'. Now the JSDoc and the API page list it with verdict, cause and notes:
    its wording, and what it picks to say, can change in any version, so show it and never parse it. The blame's
    kind, name, ms and confidence stay data, so group on those. Blame.kind's JSDoc and the API page also
    define each of the eight kinds, which are all there are in 1.x, and say that a slow event listener is both a
    handler and a script, and that which of the two a report says, and which name it gives, can change in a minor.
  • attributeINP takes web-vitals' plain Metric, as Honeycomb's INP hook hands it over. Passing a Metric,
    whose entries are typed as PerformanceEntry, failed to compile and needed a cast. Now it compiles, and each
    entry's interactionId is read where it is a number above 0, as before. InpMetric's entries are now typed as
    readonly object[], so code that types its own values with InpMetric and reads entries[0]?.interactionId no
    longer compiles and has to narrow or cast. Callers of attributeINP are unaffected. Since 0.2.0.
  • The Versions section says what 1.x will promise, and SECURITY.md says what the library touches on a page.
    Versions said only that a 0.x minor may break things. Now it says that from 1.0.0 a field keeps its meaning, an
    option keeps what it does and a default stays for all of 1.x. A minor may change which kind, name and detail a
    blame gets, which components a report names, and any display text, and each release lists its blame changes. The
    0.x rules hold until then, and CONTRIBUTING has the report, deprecation, warning anchor and inert twin rules to
    match. SECURITY.md now says which releases get security fixes, and under "What it touches" what the library
    reads, changes, keeps and lets out on a page, including the DevTools hook it wraps, the React marks it reads,
    what it stores and what a report can carry. The README's labels and personal data section points at it.
  • overlay: true no longer shows the badge in a browser where someone pressed Hide for me, a new button in the
    panel.
    With overlay: true in a shared config, the only way to lose the badge was to take it out for everyone.
    Now Hide for me, beside Clear, sets localStorage react-inp-blame to hidden, and the badge stays off in that
    browser while reports, the DevTools track and the API go on. ?inp-blame in the URL brings it back. A badge that
    overlay: 'query' or mountOverlay() shows has no Hide for me. Under 'query' the key already holds the
    'overlay' that opted in, and removing it is the way out.
  • The panel folds quick rows, says a component re-rendered only when it did, and the badge moves off a corner the
    app's own widget holds.
    Typing at the 40 ms threshold made a row per key that read 'nothing stood out' and
    pushed the slow rows down. Now rows under 200 ms that blame nothing fold into 'N quick interactions, nothing to
    fix', which opens on a click, and max counts the rows and the fold separately, so a slow row stays in sight.
    Rows where the library can't tell stay rows. A context update read 'ProductList re-rendered · ProductRow ×375 ·
    151 ms' for a list that bailed out, and now reads 'PrefsProvider updated · ProductRow ×375 re-rendered inside
    ProductList · 151 ms', or '120 of 351 components re-rendered inside ProductList' where no one component was most
    of what rendered inside. The report's blame is unchanged. With no position, the badge sat on top of a chat
    button in the bottom right, and a click meant for the button opened the panel. Now it checks when it mounts and
    again at the first report, including widgets inside another shadow root such as Next.js's dev indicator, and
    takes the first free corner of bottom right, bottom left, top right and top left. It stays where it is when all
    four are taken, as under a dialog's backdrop. An explicit position is used as given.
  • A finger's or a pen's click reads as a tap. A touch click's verdict read '120 ms click' and its row 'Click on
    "Save"'. Now they read '120 ms tap' and 'Tap on "Save"'. A click that WebKit reports with pointerType 'mouse'
    takes the pointer from its own press's pointerdown. So pointerType in the report and in nextInput is now
    'touch' for a tap in WebKit, and a frame that waited on it says it waited on the next tap. A press whose pointer
    the library did not see keeps 'Click' in its row.
  • A layout blame is named after the render's start only where the forcing script's commit is the one the cause
    describes.
    With a Sheet closing inside the forcing script and a 500-row Table rendering later, the blame was { name: 'Dialog', detail: '56 components' } beside a cause that never names Dialog. Now it is 'DismissableLayer'
    with '15 of 56 components', as 0.18.0 had it. Since 0.19.0. The Performance panel's interaction entry takes the
    start a layout blame names, where that is the heaviest render's start: a Sheet close blamed on Dialog drew '160
    ms click · DismissableLayer', and now draws '160 ms click · Dialog'.
  • The render a layout blame names counts in the note and the Summary, however small. A 4-component Popover
    commit a layout blame named was 'too small to count', so the Summary read 'React renders before the paint 0, and
    1 too small to count'. Now it reads '1', and 'React rendered N times before the screen updated' counts that
    commi...
Read more

0.19.0

Choose a tag to compare

@adityareddy-dev adityareddy-dev released this 29 Sep 09:23

Changed

  • inp() and its interactionCount leave out the badge and panel's own clicks, taps and key presses. They
    counted them, as web-vitals and Chrome's own INP do, though no report was ever made for them, and on a phone
    opening the panel can take longer than the page's own taps. On a Pixel 7 emulator with the CPU slowed 4x, the
    panel's tap took 104 ms after a single 56 ms tap on the page, so the badge and the panel head read "Page INP so
    far 104 ms" with no "from click on" line, inp() pointed at the panel's tap with report: null, and
    interactionCount said 3 for one real tap. The panel's Clear tap could do the same right after it cleared. This
    departs from web-vitals on purpose: the library's INP and count can now differ from web-vitals' on a page where
    the badge or panel was used, and the web-vitals page lists this among the cases where the two part. After Clear
    the count starts at 0 and one tap on the page reads 1 interaction. A tap there too quick to send an Event Timing
    entry, under 16 ms, is still counted, since nothing says where it landed. Since 0.1.0.
  • A forced layout is named after the component the render started from where that holds the whole commit.
    Closing a shadcn/ui Sheet in a production build gave blame { kind: 'layout', name: 'DismissableLayer', detail: '15 of 56 components' }, a part of the commit that held none of the read: Radix's Presence reads
    animationName in a layout effect, above DismissableLayer and beside it. It now gives name: 'Dialog' and
    detail: '56 components', the component the cause already said the render went "from ... down". Where the render
    started at one root of several, or where the component rendered many times over is the one the render is named
    after (a TreeNode in a tree of them), the name and detail are as before. A render blame on the same commit is
    unchanged, so the two can now name different components, and a dashboard that groups layout blames by name sees
    these under the new one. Since 0.13.0. The layout sentence also names its window once and says what forces a
    layout before what React rendered: "Of the 116 ms it took to handle the click, the browser spent 108 ms
    recalculating styles and layout, leaving 8 ms for ... That happens when code reads an element's size right after
    changing styles, often in a layout effect. React was re-rendering 181 components inside Tabs." It said "spent"
    twice, and "That happens" came after the re-render and read as being about it. Since 0.2.0.
  • A screen update blamed on React's own task names the component React rendered inside it. Where the screen
    update outranked the working time and the script after the handlers was React's scheduler task, blame.name was
    'MessagePort.onmessage', and on a phone the row read "screen took 148 ms to update · MessagePort.onmessage", in
    bold the name of a task no page ever writes. Now blame.name is the component that render is named after,
    'CascadingEffect', and the row reads "screen took 148 ms to update · CascadingEffect". The cause still says "a
    script (MessagePort.onmessage, app.js) ran for 96 ms before the next frame, and React rendered inside it". Any
    other script keeps its own name, as in 'DIV.onscroll'.
  • An icon a script drew in a control is put on the control's component. feather.replace(), and Font Awesome's
    autoReplaceSvg and searchPseudoElements, draw an <svg> React never saw, so it is read from the element holding
    it. With <IconButton onClick={close} /> rendering <button onClick={onClick}><i data-feather="x" /></button>,
    the click was climbed past IconButton along with the handler it was handed, and target.owners and
    generateTarget read "Page (svg.feather.feather-x)". They now read "Page > IconButton (svg.feather.feather-x)",
    as they do for an svg React renders there. That covers a button, and a <div role="button"> whose role
    IconButton wrote itself, also on a styled component that renders the div, whatever else the control holds. In
    React 18 and 19, since 0.6.0.
  • React's render time across several commits is said as their total, with the named commit's share. The
    handler, layout and render sentences put all of it on the one commit they named: "React spent 55 ms re-rendering
    30 components inside List" gave List a 500-component Sidebar's 25 ms, and a render verdict three 3 ms renders
    earned together read "React spent 3 ms". Now it reads "React spent 55 ms rendering across 2 commits, 30 ms of it
    re-rendering 30 components inside List". A hedged sentence, and a note standing in for a closed render verdict,
    keep the named render against the working time and add ", and 55 ms of rendering in all across 2 commits" after
    it. Renders under 1 ms count in the total too, where six of them beside List's 30 ms read as "React spent 32 ms
    re-rendering 30 components inside List".
  • "React rendered N times" counts a small render whose effects took the time, and the renders the sentences
    count.
    Take a click whose 3 ms render ran 100 ms of useEffect that set state, followed by a 200 ms re-render of
    400 components. It had no note about the second render, and now notes adds "React rendered 2 times before the
    screen updated, which usually means a state update inside an effect or a chain of updates." A render counts once
    its committing or effects are worth a mention, and the render a verdict blames always counts. The note and a
    cause's "rendering across 3 commits" count the same renders, where the note used to leave out one under 5 ms. A
    hydration still isn't counted as a re-render, except where the sentence gave the render time across commits with
    the hydration among them. Since 0.1.0.
  • A key press no longer names the next click's listener as its own script, or says its own timer was the next
    key's wait.
    A keydown whose keyup was not handled in its frame, followed before the paint by a click whose
    button went down after the key press, read "React's render was small (re-rendering 3 components inside Editor,
    mostly Row (3 of them)); a script (DIV#root.onmousedown, app.js) ran for 56 ms after the handler finished." for
    that click's 56 ms onmousedown, and any key press did the same for its onclick, onpointerup or onmouseup.
    The blame is now none, and the note says "After the handler finished, the screen took another 92 ms to update:
    the frame waited on the next click, which the page handled first. The longest script the browser recorded in that
    time was DIV#root.onmousedown (app.js), 56 ms." The same goes after a click's handlers. A pointerdown still has
    its own onmousedown and click listeners to dispatch, and a pointerup whose click was too quick for an entry its
    click listeners, so each keeps them as its script. A click whose button went down before the key press, or whose
    pointerdown was not recorded, is weighed as before, since nothing tells its listeners from the key's. So is a tap
    whose finger went down before the key press, which dispatches its mousedown only once the finger lifts. Under a
    screen update of 100 ms or less, where the next key went down before the paint but no listener of its was
    recorded and nothing showed it held the frame, a keydown's own 86 ms timer read "After the key press was handled,
    the screen took another 90 ms to update: the frame waited on the next key press, which the page handled first."
    It now reads as it does without the next key, "..., mostly because a script (TimerHandler:setTimeout, app.js) ran
    for 86 ms before the next frame.", and under a 40 ms screen update the blame moves from painting to script.
    Where the next key's render ended by the paint, half the screen update or more after the handlers, the painting
    blame stays and says "the frame most likely waited on the next key press". In React 18 and 19, the click's
    onclick since 0.16.0 and the timer since 0.13.0.
  • Where the handlers' own script decides an idle click's verdict, a longer timer after it goes in the note. A
    click that rendered nothing, whose handlers included a 22 ms script, followed by a 49 ms timer under a 100 ms
    screen update, read "React didn't render anything; a script (TimerHandler:setTimeout, app.js) ran for 49 ms after
    the handler finished." It now reads "React didn't render anything; the click handler handleSave ran for 22 ms.",
    and the note says "After the handler finished, the screen took another 100 ms to update: 50 ms of it was the
    browser's own work on the main thread, most likely recalculating styles and layout for what changed. The longest
    script the browser recorded in that time was TimerHandler:setTimeout (app.js), 49 ms." In React 18 and 19, since
    0.16.0.
  • The READMEs call it a development tool first, production optional. The comparison table's first row read
    "Production-safe" with a yes for this library. It now reads "Runs in a production build" with "optional", and
    both READMEs give what 0.12.0 cost where it was turned on: about 209 ms of page load on the shadcn/ui docs site,
    and about 5 ms inside each interaction on the twenty CRM. The docs also say that under labels: 'attributes' an
    aria-label, test id or id built from user data goes out as your code wrote it, in target.label,
    target.selector and generateTarget's string, that react-scan's lite and all-environments entries run on
    production builds, and that each report goes out as a User Timing measure any PerformanceObserver on the page
    can read.

Fixed

  • A render is placed where it ran against the handlers, without another commit's effects. In a production
    build, 800 rows committed after 30 ms of handlers read "In 30 ms of working time, short of a long task, React was
    re-rendering 800 components inside List". It now reads "After the 30 ms...
Read more

0.18.0

Choose a tag to compare

@adityareddy-dev adityareddy-dev released this 29 Sep 02:25

Changed

  • The Performance panel track is drawn only under a development build of React unless you ask for it. Each
    report went out as a User Timing measure in production too, so any script on the page with a PerformanceObserver
    (an analytics tag, a chat widget) received its verdict and target label, even though the entry is cleared
    straight away. devtoolsTrack now defaults to 'auto': on under a development build of react-dom, off under a
    production or profiling one, so profiling a production build needs devtoolsTrack: true. As with labels, a
    page where no react-dom has registered with the library draws nothing until one does.
  • followUps can hold a render from before the paint the report is about. Every entry used to land after
    end, so at - end was how long after the paint it came. A key's render before its slower keyup now lands
    before end, after the keydown painted, and that difference is negative for it, in the web-vitals
    react.followUps too. The DevTools interaction entry's property "React renders after the paint" is now "Later
    React renders", since it counts these.
  • A production page the sample leaves out no longer warns about a browser without Event Timing. In Safari
    before 26.2 and Firefox before 144 every page view printed "[react-inp-blame] this browser has no Event Timing
    interactionId (Chrome 96, Firefox 144, Safari 26.2), so nothing was installed." whatever sampleRate said, and a
    tool that forwards console warnings, such as Sentry or Datadog, sent it as one event per view. Now a production
    build prints it only on the pages sampleRate takes, rolled once per page, and a development build always does:
    one whose bundler wrote anything but 'production' for process.env.NODE_ENV. A page loaded with no bundler
    counts as production. stats() is unchanged, and with sampleRate at its default of 1 nothing changes. Since
    0.1.0.
  • The README is now what a newcomer reads first. It went from 1,477 lines to 329: setup for Next.js and Vite,
    what you will see, when the blame is wrong, how it compares, versions and what it costs. The full setup, the API,
    how it works, the known limits, troubleshooting and the web-vitals page are in docs/. Every link a warning
    prints still lands on its entry, through the README line that keeps its anchor and links on to docs/. The Vite
    plugin's advice for TanStack Start now says to create src/client.tsx "as the setup shows", not "as the README
    shows".

Fixed

  • On Next.js 15.3 to 16.2, a production build that enabled leaves out no longer ships the library. The
    next-client line in instrumentation-client.ts brought it into every build, about 30 KB gzipped on a minimal page,
    where it never ran. withInpBlame now sets env.REACT_INP_BLAME_NEXT to '' in a run it leaves out and with
    runtime: false, so the config comes back with that one entry, and next-client compiles to an empty
    onRouterTransitionStart there under webpack and Turbopack, on a 16.3 app that kept the line too. Since 0.3.0.
  • A Next.js config exported as a Promise keeps its settings. withInpBlame(new Promise(...)), or an async
    function called in place, came back under next dev holding only the wrapper's own keys, so the app lost its
    basePath, rewrites and env in silence. withInpBlame now wraps what the Promise resolves to, as it does for a
    config written as a function, and its types take a Promise. Since 0.1.0.
  • A key's render before its slower keyup stays in the report as a later render. A key press whose keydown
    painted in 24 ms and then rendered 400 components at 150 ms was published for that render. Once the keyup's 48 ms
    entry headed the next revision, commits and followUps were both empty and the verdict said React didn't
    render anything. Now the render stays in followUps and the verdict reads "... React didn't render anything in
    the working time. A React render landed 126 ms after the press updated the screen, before the release: 60 ms
    re-rendering 400 components inside List. INP doesn't count it, but people still wait for it." The same goes for a
    Space held down before the slow click it makes. The panel says it rendered "after the press painted", and the
    Performance panel draws it as a "Later render" from where it began. This covers key presses only. A pointer's
    render before its release is still left out, since the hook stamps a drag's move renders with its pointerdown in
    React 18 and 19.0, in production builds and on touch, and keeping them would publish a quiet drop. So is a key's
    render where the keydown sent no entry of its own, one inside another of the interaction's entries, and one after
    a newer input. Since 0.1.0.
  • A render stamped with a key's release no longer goes to that key when another key went down in between.
    Typing fast, keys roll over: B goes down before A comes up, and the results list B's keystroke asked for renders
    outside any event handler, stamped with A's keyup. A's report took it as "A second React render landed 70 ms
    after the screen updated: 60 ms re-rendering 400 components inside List." That could get A's quiet key press
    published, while B's report held nothing. A Shift let go after a click put the click's render on Shift the same
    way. Now, if a keydown, pointerdown or click from another interaction came between a release and its own press, a
    render made outside a dispatch and stamped with that keyup or pointerup is attached to nothing. One stamped with
    a click, or made inside the release's own handlers, is kept as before.
  • The Performance panel draws a render the report counts before the paint as the interaction's own. A render
    that committed after the paint as the rounded duration puts it, but while the handlers still ran, is in
    commits, and it was still drawn as a "Later render", as if it came after the paint. A 200 ms click whose
    handlers ran to 205 ms, with a commit at 203 ms, now draws "React render · OrderSummary (801 components)".