Repository navigation
Fix performance with AI only on traces worth fixing, saying what it found; AI actions only where they can run - #5083
Merged
Merged
Conversation
… saying what it found
The trace page drew 'Fix performance with AI' on every trace, fast or slow,
and the server then refused most clicks ("no deterministic performance
pattern"). Now the page asks the server first and shows the action only on a
trace worth fixing, next to what was found in plain words.
- The span-tree analysis moves to Common/Utils/Traces/SpanTreeAnalyzer so the
server and the Dashboard share it.
- Common/Utils/Suggestions holds the rules for when a suggestion shows, one
helper per kind: SuggestionOffer (None/Act/SetUp/InProgress), AiSuggestion
(the one AI switch), CodeFixSuggestion (AI on, applies, a fix in flight,
a repository) and TracePerformanceFixSuggestion (which findings are worth a
fix: N+1 and calls one after another at least 100 ms together, one slow
call at least 500 ms on its own, each at least a tenth of the request).
- POST /ai-investigation/performance-fix-suggestion answers what the page
draws, with the very gates the create route enforces; the create route now
also refuses with AI off and keeps only the findings worth fixing. Insight
fix routing follows the same rule.
- The project list carries enableAi, and the Dashboard follows its switch,
so AI actions can be hidden with AI off without a request.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… Fix performance with AI Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eep a tab picked before the first effects TraceSignals reset its tabs in a mount effect, after the first paint, so a tab picked in that window snapped back to Logs. The reset now runs only when the trace changes (the initial state already is the clean slate). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ine suite covers the fix - Draft with AI (notes), Generate with AI (incident and episode postmortems) and Explain with AI (log error patterns, the investigation drawer) are left out when the project's AI is switched off (Common/Utils/Suggestions/AiSuggestion via Utils/UseAiOffered, which follows the Enable AI switch as it is saved). - Trace detail offline fixture: a report trace with a 960 ms N+1, scenarios for AI off, no repository and a fix in flight; the suggestion route is answered by the real shared analysis and rule. Positional placeholders ($1) are kept in normalized statements so the query reads as written. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ce only where they can start - POST /ai-investigation/telemetry-improvement-suggestion answers what a service's Logs or Traces page offers (Common/Utils/Suggestions/ TelemetryImprovementSuggestion): the card with AI on and a repository, a link to an improvement already on its way, or nothing. The card draws exactly that and brings its own spacing; the create route now also refuses with AI off. - An exception's AI Assistance leaves its side menu while AI is off, its page says AI is off and who can turn it on, and the server refuses the task the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tences in 16 languages Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… buttons only where they can start; a guard holds every suggestion to its rule The traces explorer's tip and the Code page's "Where fix tasks start" say when each button shows, in all 17 languages, with the numbers and wording pinned to the code by the docs facts suites. The investigation card's Open Fix PR from this analysis shows only while AI is on (a fix already started there keeps its link). SuggestionRulesGuard holds the rules across the product: the suggestion helpers stay pure, the worth-fixing thresholds are written once, every AI action in the Dashboard asks useAiOffered(), no page reads enableAi itself, and every server code-fix creator asserts the AI switch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gestions The Dashboard locales keep master's keys (runbook variables) and add this branch's 24 sentences after them, in all 17 files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e switch columns wiring test knows Enable AI The Dashboard type-check needs EventNoteKind's type argument where the composer keeps "Draft with AI" only while AI is on. The selected project now follows Enable AI as well as Monitor Groups, and the project list selects it, so the wiring test holds both. Two of this branch's tests type their jest mocks the way @jest/globals declares them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
npm run compile-tests reads a mapped table as an array of either element, which the rows' callbacks do not accept; each row is now a typed pair. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t 28's mock types Mock comes from jest-mock and requireActual's module is cast, as the other Dashboard tests do, so npm run compile-tests type-checks Tests/App clean. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
…shows them UI review of the trace page's "Fix performance with AI" strip: - The next step sits under the finding, lined up with its words, until lg, and beside them from lg up. From md it squeezed the words into a narrow column (four lines for the German headline at 768-900px). On a phone the fix button spans the strip, as the header's buttons do; the working line and the Connect GitHub link line up with the words instead of the strip's edge. - A clock marks the finding (the bolt is the fix's, and Critical path's); the GitHub mark on Connect GitHub; Code's live dot on "OneUptime AI is working on a fix", which is now a status for screen readers; one quiet link style with a keyboard focus ring; the query on up to two lines. - A refused click asks the server again: a fix someone else started is linked, and a repository disconnected since the page loaded turns the button into the Connect GitHub link - in the strip's words, not the server's. Other refusals keep their reason beside the button. - 'Show them' below xl opened the first span as a sheet over the very spans it was asked to show. Now the spans the search marks are left in view there; one span, or spans with no shared name, are still opened. At every width it lets go of filters that would hide them and scrolls the waterfall into view. - On a phone the span search was squeezed to nothing beside the two filters, its match count and arrows spilling out of the card (on master too): it has a row of its own. Tests: component (where the next step sits per offer, the query's lines, the working status), TraceExplorer (three refusal re-reads, 'Show them' wide, narrow, one span, filters), offline E2E (repository gone after load, wide and tablet geometry, phone alignment and 'Show them' with the toolbar). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…refused improvement links the one on its way - An exception's AI Assistance page with the project's AI switched off (the side menu no longer offers it, but a link can still lead there) showed an info alert with no way forward. It now shows Code's own notice - the tasks this page starts are Code's: "AI is turned off for this project." with Turn AI on for whoever may change Enable AI, who can for everyone else, and the sentence alone before the permissions arrive. - Improve logging / tracing with AI: a refused start asks the server again; an improvement someone else started meanwhile is linked like any other on its way instead of being reported as a refusal. Tests: ExceptionAIAssistance (owner, member, permissions not yet known), TelemetryImprovementCard (refusal re-read), offline E2E ExceptionDetail (?enableAi=off: no AI Assistance in the menu, the notice with Turn AI on). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…language; the fix button fits a phone
- "Fix performance with AI", "Show them", "View task progress" and the
improvement card's titles and states were still English in 12 to 15
locales, so the strip read "... Show them [Fix performance with AI]" in
Russian, Italian, Chinese and the rest. Translated in all 16.
- The fix button's label broke into two lines on a 390px phone in German,
French, Spanish, Italian, Portuguese and Dutch: it says "speed up with
AI" there, as Russian does ("Mit KI beschleunigen", "Accélérer avec
l'IA", ...).
- The docs (Traces Explorer, Code) name the button as each language's
Dashboard now draws it, in those 15 languages.
Tests: TracePerformanceSuggestionInLocale holds every language to its own
words for the strip's button, links and states, and every language's
button label to a phone's one line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gestions-ui-review
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
Fix performance with AI sat in the header of every trace, fast or slow. Clicking it on most traces only got the server's refusal ("No deterministic performance pattern was found"), and even when the span-tree analysis found a "pattern", it was often a 3 ms query that was "most" of a 4 ms request, or a one-span health check that is "100%" of itself. The same shape repeated across the product: AI actions offered where they could not run (AI switched off, no repository for the pull request, a fix already on its way).
What changed
1. The trace page: "Fix performance with AI" only on a trace worth fixing, saying what it found
SELECT * FROM line_items WHERE invoice_id = $1· "Together they take 960 ms of this 1.72 s request. Show them" · [Fix performance with AI]The rule (decided as a product designer): a trace is worth fixing when the span-tree analysis finds a known pattern that costs the request real time:
Why absolute floors and not "slower than this endpoint usually is": an endpoint that always runs the same query 40 times is the one most worth fixing, and it never looks slower than its own usual; the floors need no query beyond the trace itself, so the page's suggestion and the server's create gate decide from exactly the same findings. A slow trace with no known pattern gets no button: the AI would have nothing grounded to fix (the server refuses it too). The 500 ms floor for "one slow call" keeps ordinary calls (a 300 ms payment provider call, a 150 ms query) from asking for a pull request.
One analysis, shared by the page and the server. The analysis moved to
Common/Utils/Traces/SpanTreeAnalyzer.ts(pure). The rule isCommon/Utils/Suggestions/TracePerformanceFixSuggestion.ts. The page asksPOST /ai-investigation/performance-fix-suggestion, which runsFixPerformanceTaskTrigger.getSuggestionForTrace— the same gatescreate-performance-fix-taskchecks a click against (AI on, a finding worth fixing, a repository, no fix already in flight). The analysis needs the spans' attributes (db statements group the N+1), which the trace page does not load, so the server answers; a project with AI off is not even asked. A test runs every fixture trace × AI on/off × repository/none × run in flight/none and holds that the page offers Fix exactly when the create route accepts the click.AI off or no repository — decided:
The server's create route now also refuses with AI off (
AI_DISABLED_MESSAGE) and keeps only the findings worth fixing in the task's evidence. Insight fix routing (the automatic latency path) follows the same rule.normalizeDbStatementkeeps positional placeholders ($1) so the query reads as written (linear pattern, 400k-character timing test).2. The audit: every suggestion shows only when it applies
POST /ai-investigation/telemetry-improvement-suggestion; create route refuses with AI offHow AI-off is known without a request: the project list (
list-user-projects) now selectsenableAi, the Dashboard follows Enable AI's saves (SelectedProjectSwitches), andUtils/UseAiOffered(useAiOffered()/isAiOfferedForSelectedProject()) answers from the selected project throughCommon/Utils/Suggestions/AiSuggestion.isAiOffered— fail open (an unknown value reads as AI on; the server refuses either way).The Common helpers (for later tasks — profiles-product-overhaul follows them)
packages/Common/Utils/Suggestions/— pure, shared by server and Dashboard, one per kind of suggestion:SuggestionOffer—None | Act | SetUp | InProgress, andparseSuggestionOffer(unknown → None).AiSuggestion.isAiOffered(project)— the one AI switch (=== false, fail open).CodeFixSuggestion.getCodeFixOffer({isAiEnabled, applies, hasActiveFix, hasConnectedRepository, isBackedByEvidence})— the shared decision for "AI opens a pull request" suggestions.TracePerformanceFixSuggestion—WORTH_FIXING_MIN_MS,WORTH_FIXING_MIN_PERCENT_OF_TRACE,isFindingWorthFixing,getFindingsWorthFixing,getTracePerformanceFixOffer, the wire format and its parser.TelemetryImprovementSuggestion—getTelemetryImprovementOfferand its wire format.A new suggestion adds a helper here, its server route answers with it (the same gates as the action's create path), and its page draws only what the answer says.
SuggestionRulesGuardholds it.What changes for existing customers
create-performance-fix-tasknow refuses traces with nothing worth fixing (same message the docs give) and refuses with AI off.Tests
Common/Tests/Utils/Suggestions/TracePerformanceFixSuggestion.test.ts— realistic span trees through the real analysis: slow checkout with an N+1 (offered, named), fast request with the same loop, health check, fast clean request, slow request with no pattern, ordinary 300 ms payment call, long batch with a small N+1 (all not offered), calls one after another, one slow query, one slow span (offered); thresholds at their edges; AI off / no repository / fix in flight; wire format round trip and every garbled answer.SuggestionHelpers.test.ts,TelemetryImprovementSuggestion.test.ts— the shared offer, the AI switch, every combination of the code-fix inputs.Tests/Utils/Traces/SpanTreeAnalyzer.test.ts(moved) — plus positional placeholders and a 400,000-character linear-time test.FixPerformanceTaskTrigger.test.ts— AI off, nothing worth fixing, only worth-fixing findings stored, the suggestion's every branch, span loading under the caller's permissions, and the server agrees with the page (10 traces × AI × repository × run in flight).TelemetryImprovementTaskTrigger.test.ts— AI off refusal, every suggestion branch, the agreement matrix for both recipes.AIInvestigationPerformanceFix.test.ts— both trace routes end to end;ExpiredSessionAIRoutescovers the two new routes.FixRouting.test.ts,InsightFixRouting.test.ts,TelemetryExceptionCodeFixRun.test.ts— routing only worth-fixing findings; exception fixes refused with AI off.TraceExplorer.test.tsx(17 in a new block, replacing the old two: offers, refusals, AI off, garbled answers, Show them, refresh),TracePerformanceSuggestion.test.tsx,TracePerformanceSuggestionInLocale.test.tsx(German, Japanese, Russian from the shipped locales),TelemetryImprovementCard.test.tsx,UseAiOffered.test.tsx,ErrorPatternExplainWithAi.test.tsx, plus AI-off cases inEventNotes,PostmortemPageParts,InvestigationArtifacts,InvestigationPanel,ExceptionAIAssistance,ExceptionDetailSideMenu,SelectedProjectSwitches;ProjectSettingsSwitchesSaveOnFlipholds Enable AI among the selected project's switch columns and in the project list's select.Common/Tests/App/Dashboard/SuggestionRulesGuard.test.ts— helpers pure; thresholds written once; every Ask-AI-with-a-prompt opener, every Generate-with-AI renderer and every code-fix starter asksuseAiOffered(); no page readsenableAiitself; every server code-fix creator asserts the AI switch.packages/E2E/TraceDetail): the fixture answers the suggestion route with the real shared analysis on its own spans; newreporttrace; 10 new specs (no button on the checkout trace, the finding and the fix, task link, refusal, Connect GitHub, fix in flight, AI off, failed suggestion, Show them, phone layout). The whole suite: 42 passed in 1.3 min locally (+~16 s for the new specs).TelemetryExplorersDocsFactsandAiDocsFactspin the numbers and wording to the code.Also fixed on the way
TraceSignalsreset its tabs in a mount effect after the first paint, so a tab picked in that window snapped back to Logs (the metrics tests were timing-lucky). The reset now runs only when the trace changes.Left out
i18n:checkreports the files sound.What ran (after merging master)
*Guard*suite, the side-menu guards): 254 passed, 1 skipped; 8,899 tests.ee/in place and again withee/moved aside. The first run found two things, both fixed here: the switch-columns wiring test now expects Enable AI, and a service worker my local build generated (public/sw.js, ignored by git) tripped a pattern guard until it was removed.packages/E2E/TraceDetail: 42 passed (1.3 min).npm run compile-tests(Common),npm run compile(App, E2E) and the Dashboard'stsc: clean.prettier --checkandeslinton every changed file: clean. The branch's ReDoS check found 0 patterns that are not proven linear (the moved analyzer's patterns were checked separately: all linear).npm run i18n:check: every locale sound.UI review
What I looked at. The real trace page (the offline fixture, with production's Inter font, Theme.css dark remaps and the Dashboard's locale files) at 390, 640, 768, 900, 1024, 1280 and 1440 px, light and dark: every finding's wording (the same query again and again, the same call, queries and calls one after another, one slow query, one slow call, a 120-character parent name, a long SQL statement), every state of the strip (the fix, its loading state, Connect GitHub, a fix on its way, after the click, a refusal), "Show them" at each width, and the strip in German, French, Spanish, Italian, Russian, Japanese, Chinese, Hindi and Persian (the fix button in all 17 languages, measured). The improvement card on a service's Logs and Traces pages (offered, its confirm dialog, created, in progress, refused) at 1280 and 390 px, light and dark, in English, German and Russian. An exception's AI Assistance page and side menu with AI off (the offline exception fixture). The AI-off removals (Draft with AI, Generate with AI, Explain with AI, Open Fix PR) in code: nothing is left behind (no empty toolbar, gap or card).
What I changed, and why.
Tests added or changed.
TracePerformanceSuggestion.test.tsx: where the next step sits for each offer (and only the fix button spans a phone), the query's two lines, the working line's status role, live dot and link.TracePerformanceSuggestionInLocale.test.tsx: every language words the strip's button, tooltip, links and states itself, and every language's button label fits a phone's button on one line (the six old labels fail it).TraceExplorer.test.tsx: three refusal re-reads (someone else's fix → its link; repository gone → Connect GitHub; the re-read fails → the reason stays) and a "Show them" block replacing the one test (wide: opens the first and scrolls; narrow: no sheet; one span; lets go of errors only and service chips).TelemetryImprovementCard.test.tsx: a refusal while the same improvement started meanwhile links it; the refused-start test checks the re-read.ExceptionAIAssistance.test.tsx: AI off for an owner (Turn AI on → AI Features), a member (who can) and before permissions arrive.TraceDetail+6 specs and a?repo=gonefixture scenario (repository gone after load; the fix beside the finding on a wide screen; two tablet layouts; phone alignment of the link and the working line; phone "Show them" with the toolbar), about 15 s more in Dashboard Offline UI group 3.ExceptionDetail+1 spec and?enableAi=off(no AI Assistance in the menu, the notice with Turn AI on), about 5 s more in group 2. No regrouping needed.Left for later (not UI polish): once a performance fix has finished, the trace offers the fix again (the server's de-duplication looks at runs in flight only); linking the finished fix's pull request instead is a server change. Persian pages render left to right across the Dashboard, the strip included (pre-existing). Durations read "1.72 s" in every language (the trace page's formatter, pre-existing).
What ran for the review (after merging master at 603b51d): Common's guard suites (150 suites, 4,751 tests) and the suites that read what the review changed, the locale suites included (42 suites, 1,645 tests); App's root-level tests (17 suites), its locale and wiring suites (11 suites); the docs suites that read the two changed pages (13 suites, 9,126 tests), every docs translations suite (47 suites, 73,006 tests, as a Dashboard label the docs write changed in 15 locales) and the general docs suites (29 suites); the changed suites again with
ee/moved aside;docs:check-anchors,docs:localize-anchors(nothing to move),i18n:validate,i18n:check; the offline browser suitesTraceDetail(48 passed) andExceptionDetail(32 passed);compile-tests(Common), the App, Dashboard and E2E compiles, the--listFilespackage check,prettier --checkandeslinton every changed file, and the ReDoS check (no new pattern). Not run: the full Common and App suites, and the other browser suites.🤖 Generated with Claude Code