Skip to content

feat(seer): Autofix overview sections driven by issue.autofix_state#120294

Merged
NicoHinderling merged 19 commits into
masterfrom
feat/autofix-overview-state-sections
Jul 22, 2026
Merged

feat(seer): Autofix overview sections driven by issue.autofix_state#120294
NicoHinderling merged 19 commits into
masterfrom
feat/autofix-overview-state-sections

Conversation

@NicoHinderling

@NicoHinderling NicoHinderling commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

On top of the existing autofix overview page work, this changes how the page's five sections (Awaiting your review / Code changes ready / Ready to generate code / Needs investigation / Merged) are populated:

  • Each section is now its own issue-stream query against the new issue.autofix_state search key, with true per-section counts from the X-Hits header — replacing the client-side classification that fetched full run state from Seer per issue (2 Seer round trips each) and could only count the current page of 10.
  • Sections fetch up to 100 issues in one request each; the global cursor pagination is removed.
  • Card content (root-cause/fix prose, diff stats, PR state) now loads viewport-lazily per card via LazyRender, so off-screen cards cost nothing.
  • The Outcome and Needs-attention filters are removed; Projects, Activity period, and Sort remain and map onto the section queries server-side.
  • getStatusGroup/buildOverviewRows client classification deleted; the card/table view toggle is unaffected.

Depends on the backend search key landing first: #120293. Until it does, the flag-gated page renders its error state.

@github-actions github-actions Bot added the Scope: Frontend Automatically applied to PRs that change frontend components label Jul 22, 2026
@NicoHinderling
NicoHinderling force-pushed the feat/autofix-overview-state-sections branch from d534132 to de53999 Compare July 22, 2026 16:39
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

📊 Type Coverage Diff

Metric Before After Delta
Coverage 94.02% 94.03% 🟢 +0.01%
Typed 136,293 136,403 🟢 +110
Untyped 8,667 8,666 🟢 -1
🔍 3 new type safety issues introduced

Non-null assertions (!) (2 new)

File Line Detail
static/app/views/seerWorkflows/overview/buildOverviewRows.ts 48 [...PIPELINE].sort((a, b) => b.fill - a.fill).find(stage => reached[stage.key])!
static/app/views/seerWorkflows/overview/useAutofixSections.tsx 61 results[index]!

Type assertions (as) (1 new)

File Line Detail
static/app/views/seerWorkflows/overview/statusGroups.tsx 30 as Record<AutofixStateKey, number>Object.fromEntries( PIPELINE.map(stage => [stage.key, stage.fill]) ) as Record<…

This is informational only and does not block the PR.

Squash of feat/autofix-overview-state-sections. Rebuild the Seer autofix
overview cards around a single narrative flow (progress ring, proposed
fix / diagnosis body, next steps, inline file differ, deep-link focus
mode, project selector, table view toggle, Investigate fallback CTA)
and drive the overview sections from issue.autofix_state activity data.
The overview only ever renders the five AutofixStateKey status groups, so
the awaiting_input/errored/running status-group metadata, their tooltip
description path, and the widened StatusGroupKey union were unreachable.
Drop them along with the unused ATTENTION_REASONS export, the write-only
OverviewRow.lastSeen field, and the never-read culprit/seerFixabilityScore
fields on OverviewIssue.
Sections carried isError/refetch that nothing rendered; a failed bucket now
shows a retryable LoadingError inline while its siblings keep loading.

The period filter's 'All time' option was a lie: statsPeriod fell back to
'90d' either way. Replace it with an explicit 'Last 90 days' default and
thread the active period through the row so the count tooltip states the
real window instead of a hardcoded 90 days. lastActivityAt now takes the
most recent of its candidate timestamps rather than the first defined one,
and the diff-size pill is focusable so its churn tooltip is reachable by
keyboard and assistive tech.
Trim stale and overwrought comments across the overview (references to a
removed progress ring, a nonexistent Diagnosis block, and running/errored
groups that no longer exist), and move the buildAnalysis docstring onto the
function it describes.

Rename the client-internal analysis key reviewer_notes -> next_steps and the
AutofixRunStatus bucket COMPLETED -> SETTLED to match what the UI actually
represents (a run that has stopped, not necessarily one that finished).
Collapse the repeated staleTime literal into a shared QUERY_STALE_TIME
constant, type the attention-badge Icon with SVGIconProps like the status
groups do, wrap the autofix phase labels in t(), and dedupe the identical
event/user count-label logic in IssueCard and IssueTableRow into one helper.
The production overview was forked from autofixIssuesDemo and still imported
its RunQuestion type, duplicating the /seer/runs/ payload across three shapes
(SectionRun, OverviewRunData, and the demo's SeerRun) and the issue shape
(OverviewIssue vs the demo's Issue), plus the RUNS_QUERY and
REQUIRED_ISSUE_FILTER constants.

Make the overview the single owner: types.ts now defines the canonical
RunQuestion, RunPullRequest, SeerRun, and OverviewIssue shapes and both query
constants. The per-issue runs+state join is extracted into a shared
useIssueAutofixEnrichment hook; sectionIssueCard consumes it, dropping its
inline reimplementation, SectionRun, and RUNS_QUERY. buildOverviewRow now takes
the canonical SeerRun, deleting the loosened OverviewRunData.

The demo consumes the shared types and constants and extends OverviewIssue for
its two extra columns. It keeps its own useQueries fan-out over a page of
issues rather than the per-issue hook, which does not fit that batching shape.

No autofixIssuesDemo imports remain under seerWorkflows/.
IssueCard and IssueTableRow both rendered the headline-or-title link with the
"Raw issue title" tooltip verbatim. Extract a single IssueTitleLink component;
the only difference between the two sites was the wrapping Text size, which is
now an optional prop (the card passes size="lg", the table row uses the
default).
The overview bucketed every issue server-side via issue.autofix_state, then
each card independently re-derived its own state from the per-issue enrichment
(deriveAutofixOutcomes -> getAttentionReason). Two classifiers over two data
sources meant a card could contradict its own section header (a Merged tag
inside "Awaiting your review").

Collapse the four hand-encoded stage orderings (SECTION_ORDER, OUTCOME_ORDER,
the getAttentionReason ladder, the STATUS_GROUP_META fill numbers) into a
single PIPELINE table in types.ts. SECTION_ORDER, the section-header checklist
fill, and the focus-mode fallback precedence all derive from it.

Thread the server section key through the card and let deriveCardAction map it
to the primary action; delete getAttentionReason, deriveAutofixOutcomes, and
OUTCOME_ORDER. OverviewRow no longer shreds one status into outcomes /
prMerged / isProcessing / autofixRunStatus: it carries a single runStatus
mirroring the payload, and the Running tag, Retry/Add-context prompts,
pending-question line, and loading placeholder are transient overlays over the
section-driven action, never a second classification.

Focus mode (?id=), which has no section, reconstructs one client-side with
deriveSectionKey by walking the same PIPELINE table.
index.tsx was a ~370-line god component doing feature gating, query-param
decodes, two data hooks, localStorage state, the toolbar, focus mode, and the
section list, with its content region a five-deep nested ternary interleaving
focus and list modes.

Extract three focused components alongside it: OverviewFilters (the toolbar),
FocusedIssue (the ?id= pinned-issue query and its error / pending / not-found /
card branches), and SectionList (useAutofixSections plus the per-section
rendering). index.tsx is now a thin shell that decodes the URL, owns the
shared view and collapse state, and composes the three behind the feature gate
as a single focus-vs-list branch.

Pure mechanical extraction: no behavior change. SortValue and OverviewView move
to types.ts so the pieces can share them.
Each rendered overview card mounts two live enrichment queries, and
sections fetch up to 100 issues; with LazyRender never unmounting once
visible, a fully-scrolled page accumulated up to ~1000 live
subscriptions all refetching on window focus.

Cap each section at 25 hydrated rows via Collapsible and reveal the rest
on demand. Unrevealed cards stay unmounted (Collapsible slices before
rendering), so their queries never start until requested. The section
header badge keeps reporting the full X-Hits total.
…vations

The overview spec re-inlined near-identical run and autofix-state blocks
across many tests, some carrying fields the code never reads. Introduce
makeRun / makeAutofixState / makeBlock / makeOutput builders so each test
states only the dimension it varies, and drop mock fields nothing reads
(run type/dateCreated, output key, PR mergedAt, block id/timestamp, and
the unread repo_pr_states fields). Every prior assertion is preserved.

Add direct coverage for the pure derivation logic, which the component
tests only exercised indirectly:
- buildOverviewRows.spec: parseRootCause (pipe split, no-pipe, over-length
  headline, empty parts, emphasis strip), normalizeBulletList (inline
  bullet rewrite, no-space bullet, no-op), extractPendingQuestion (nested
  question, flat key fallbacks, blank/missing guards, status guard), and
  a table-driven deriveSectionKey precedence check down to the floor.
- cardAction.spec: deriveCardAction per stage key (review_pr carries the
  PR), and IssuePrimaryAction's pending placeholder plus the transient
  runStatus overlays that never reclassify the section anchor.
- triggerBadge.spec: mapRunSourceToTrigger mapping and the raw-source
  fallback badge.

Export parseRootCause / normalizeBulletList / extractPendingQuestion so
they can be unit-tested next to their module.
Make the overview derivations total and the period window label a
whole translatable unit:

- mostRecentTimestamp now compares candidates by parsed epoch time
  instead of lexicographically, so a higher-precision timestamp no
  longer loses to an earlier one that happens to sort later.
- normalizeBulletList trims and filters segments before prefixing
  "- ", so consecutive or trailing bullets no longer leave blank
  list items.
- periodWindowLabel returns a complete translated phrase per period
  rather than interpolating a lowercased fragment into another
  translation, and its default lookup is total (no non-null assertion).
- Correct the lastActivityAt comment: it labels the card's updated
  TimeSince, it does not drive sorting or the period filter.
Cover the overview derivations and fallback branches that had no
tests:

- buildOverviewRows: mostRecentTimestamp numeric ordering and the
  all-nullish case, the coding_agents-only deriveSectionKey path,
  buildAnalysis positional fallback and empty-answer dropping, the
  normalizeBulletList empty-item cases, and the extractPatchInfo
  file/changed-line boundaries plus multi-repo path prefixing (the
  boundaries are pinned to the exported constants).
- index: X-Hits body-length count fallback, SECTION_ORDER header
  ordering, the review_pr internal-link fallback when a run has no PR
  url, the focused-issue empty and error branches, and a section
  retry-refetch after an error.
- periods: the per-period window phrase, the unknown-period default,
  and that the default period is present in the option list.

Exports mostRecentTimestamp, buildAnalysis, extractPatchInfo, and the
inline-diff limit constants so the derivations can be tested directly.
Sections fetch at most 100 issues but the badge showed the exact X-Hits total, overstating what scrolling could reveal.
Move the primary-action render matrix down to cardAction.spec, where it
belongs, and drop the redundant full-page mounts that only re-proved it.

cardAction.spec gains an it.each covering every non-overlay section action
(merged, code_changes_ready, solution_ready — previously untested —, and
needs_investigation) plus the internal review-link fallback for a review_pr
card with no PR url.

index.spec folds the header-ordering loop, the dropped-filter negatives, and
the on-card analysis assertions into the two mounts that already render them,
then deletes the standalone header-order, filter, thought-order,
internal-review-link, Investigate, and merged-tag tests. Section-wiring stays
proven by the section-authoritative merged test. Net: fewer full-page mounts,
same behavioral coverage.
@NicoHinderling
NicoHinderling marked this pull request as ready for review July 22, 2026 20:26
Comment thread static/app/views/seerWorkflows/overview/sectionList.tsx
Comment thread static/app/views/seerWorkflows/overview/sectionList.tsx
Comment thread static/app/views/seerWorkflows/overview/sectionList.tsx Outdated
Comment thread static/app/views/seerWorkflows/overview/useAutofixSections.tsx
Comment thread static/app/views/seerWorkflows/overview/buildOverviewRows.ts
Errored sections were counted as empty, so a page with some sections
failing and the rest empty showed the global "no runs" box and never
rendered the per-section load error. Exclude errored sections from the
all-empty check so the section list and its inline error surface.

Show a filter-aware empty message when a project is selected or a
non-default period is active, so an empty result under filters reads as
"no matches" instead of "no runs yet".

Expose enrichmentPending (state or runs query pending) from the enrichment
hook and drive the card's primary-action placeholder from it. In focus
mode the section is derived from enrichment, and merge evidence lives only
on the run, so deriving before the runs query settles could transiently
misclassify a merged issue.

Replace the isLast border prop with a :last-child CSS rule on the table
rows' container, dropping the prop from the row and every call site.
Remove the 25-row render cap and the Collapsible show-more/less wrapper so
sections render every fetched issue. The fetch limit and the 100+ count
badge are unchanged.

Because there is no longer a cap, height-stabilize lazy cards: while
enrichment is pending the mounted card reserves at least the LazyRender
placeholder height, so newly mounted cards can't collapse and cascade
intersection mounts. The constraint releases once enrichment settles.

Update tests: drop the show-more cap test in favor of asserting all 30
fetched issues render, cover the errored-plus-empty and filtered empty
states, and select two projects so the plain empty-state test keeps an
empty (no-filter) selection.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9b522b3. Configure here.

sectionKey?: AutofixStateKey;
}) {
const {run, state, enrichmentPending} = useIssueAutofixEnrichment(issue.id);
const row = buildOverviewRow(issue, run, state, enrichmentPending, statsPeriod);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action waits on runs fetch

Medium Severity

buildOverviewRow is given enrichmentPending for the row's statePending field, but that field is documented and consumed as “autofix state still in flight.” IssuePrimaryAction therefore keeps showing the placeholder until the runs request finishes too, even though section actions and live-status overlays only need state. Cards stay actionless longer than necessary after state has already resolved.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9b522b3. Configure here.

<Text as="p" variant="muted" align="center">
{hasNonDefaultFilters
? t('No autofix runs match your filters.')
: t('No completed autofix runs yet.')}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong empty-state filter check

Low Severity

hasNonDefaultFilters treats any non-empty projects selection as an active filter. Page filters auto-select the only project in single-project orgs, so those users get “No autofix runs match your filters.” on a truly empty overview instead of “No completed autofix runs yet,” even with the default period.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9b522b3. Configure here.

@NicoHinderling
NicoHinderling merged commit 02281fd into master Jul 22, 2026
64 checks passed
@NicoHinderling
NicoHinderling deleted the feat/autofix-overview-state-sections branch July 22, 2026 21:23
NicoHinderling added a commit that referenced this pull request Jul 22, 2026
…20397)

List cards now drop the action placeholder as soon as the autofix state
request resolves, instead of also waiting on the runs request, which
only feeds the analysis prose and trigger badge. Focus mode (`?id=`)
still waits for both requests before classifying the card, since merge
evidence comes from the run's pull requests and classifying early could
briefly mislabel a merged issue as awaiting review.

The empty-state copy also stops counting a forced project selection as
an active filter. Page filters auto-select the org's only project in
single-project orgs, so a truly empty overview showed "No autofix runs
match your filters." even with no filtering possible. A selection now
counts as a filter only when the org has more than one project,
mirroring the page-filters force-select rule.

Both address review findings surfaced on #120294 after it merged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants