Skip to content

ref(seer): Unify explorer state with shared context and fix optimistic updates#115579

Open
JonasBa wants to merge 14 commits into
masterfrom
jb/seer/drawer-state
Open

ref(seer): Unify explorer state with shared context and fix optimistic updates#115579
JonasBa wants to merge 14 commits into
masterfrom
jb/seer/drawer-state

Conversation

@JonasBa
Copy link
Copy Markdown
Member

@JonasBa JonasBa commented May 14, 2026

Centralizes active run id logic inside the seer context allowing us to remove the hook sync mechanism.

sequenceDiagram
    participant U as User
    participant L as Local State<br/>(reducer + sessionStorage)
    participant S as Server<br/>(query cache)

    Note over L: Init: sessionStorage → reducer seed

    rect rgb(240, 240, 255)
    Note over U,S: Page load
    S->>S: useQuery /explorer-runs/
    S-->>L: ExplorerSession[]
    L->>L: merge: sessions.map(s => {…s, status: reducer[s.id] ?? idle})
    L-->>U: {query, conversations}
    end

    rect rgb(240, 255, 240)
    Note over U,S: Switch to existing run
    U->>L: dispatch('set active run', 123)
    L->>L: reducer marks 123 active, rest idle
    L-->>L: write-through → sessionStorage
    L->>L: re-merge with existing query data
    L-->>U: runId = 123
    end

    rect rgb(255, 245, 235)
    Note over U,S: New conversation
    U->>S: POST /explorer-chat/ {query}
    S-->>L: onSuccess → {run_id: 456}
    L->>L: ① dispatch('set active run', 456)
    L-->>L: write-through → sessionStorage
    L->>S: ② setApiQueryData: prepend new session to cache
    Note over L: merge runs — new session appears<br/>with status: active in one render
    L-->>U: runId = 456, conversation visible
    end
Loading

JonasBa and others added 5 commits May 14, 2026 09:42
Introduce SeerExplorerStateProvider that sits on top of the sessions
query, enriching each session with an active/idle status derived from
a reducer. Seeds the active run from sessionStorage on mount and
persists changes back on dispatch. This is the first step toward
replacing useSeerExplorerRunId with a unified context.

Co-Authored-By: Claude <noreply@anthropic.com>
…ns providers

Consumers that only dispatch (e.g. drawer) won't re-render when
conversations change, and read-only consumers (e.g. topBar) won't
re-render on dispatch.

Co-Authored-By: Claude <noreply@anthropic.com>
Migrate all four consumers of useSeerExplorerRunId to the new
SeerExplorerStateProvider. The active run is now derived from
the enriched conversations list instead of a separate
sessionStorage-backed hook, eliminating the implicit coupling
between run ID and session data.

Co-Authored-By: Claude <noreply@anthropic.com>
When a new session is created, optimistically prepend it to the
sessions query cache so the conversations list picks it up
immediately. Without this, the active run ID had no matching
session until the list query refetched, causing a momentary loss
of active state.

Also ensure set active run resets all other runs to idle and
move persistence side effects out of the reducer into a dispatch
wrapper to keep the reducer pure.

Co-Authored-By: Claude <noreply@anthropic.com>
Extract makeSeerExplorerSessionsQueryOptions so the sessions query key
is derived from a single source. The previous hand-crafted key missed
the query params, writing to a phantom cache entry and causing the new
conversation to briefly disappear after creation.

Co-Authored-By: Claude <noreply@anthropic.com>
@JonasBa JonasBa requested review from a team as code owners May 14, 2026 17:58
@github-actions github-actions Bot added the Scope: Frontend Automatically applied to PRs that change frontend components label May 14, 2026
JonasBa and others added 2 commits May 14, 2026 10:59
Replaced by useSeerExplorerConversations from the shared state context.

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 14, 2026

📊 Type Coverage Diff

Metric Before After Delta
Coverage 93.52% 93.52% ±0%
Typed 135,612 135,625 🟢 +13
Untyped 9,398 9,395 🟢 -3
🔍 1 new type safety issue introduced

any-typed symbols (1 new)

File Line Detail
static/app/views/seerExplorer/seerExplorerSessionContext.tsx 156 parsed (var)

This is informational only and does not block the PR.

Comment on lines +22 to +24
if (next[action.payload]) {
next[action.payload] = {...next[action.payload], status: 'active'};
}
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.

Bug: The 'set active run' reducer fails to add a new conversation's run_id to the state because it only updates runs that already exist, preventing new conversations from being marked as active.
Severity: HIGH

Suggested Fix

Modify the 'set active run' reducer to handle cases where the run_id does not yet exist in the state. If next[action.payload] is falsy, create a new entry for the run and set it as active. This will ensure that newly created conversations are correctly added to the state and marked as the active conversation.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: static/app/views/seerExplorer/seerExplorerStateContext.tsx#L22-L24

Potential issue: When a new conversation is created in the Seer Explorer, the 'set
active run' action is dispatched. The corresponding reducer attempts to update the
state, but it only modifies entries that already exist. For a new conversation, its
`run_id` is not yet present in the state object. The condition `if
(next[action.payload])` evaluates to false, causing the reducer to do nothing.
Consequently, the new run is never added to the state or marked as active, and the
derived `runId` remains `null`, breaking the user flow for creating new conversations.

Did we get this right? 👍 / 👎 to inform future reviews.

Comment thread static/app/views/seerExplorer/seerExplorerStateContext.tsx Outdated
Comment thread static/app/views/seerExplorer/seerExplorerStateContext.tsx Outdated
Comment thread static/app/views/seerExplorer/seerExplorerStateContext.tsx Outdated
Fold seerExplorerStateContext into seerExplorerSessionContext so there
is a single provider that owns the query, reducer, and merge logic.
The context value is now {query, conversations}, letting consumers
destructure what they need. Removes a provider nesting layer from
the org layout.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Comment thread static/app/views/seerExplorer/seerExplorerSessionContext.tsx Outdated
Comment thread static/app/views/seerExplorer/seerExplorerSessionContext.tsx
Tests that depend on runId now wait for the sessions query to resolve
before calling functions like interruptRun that require a non-null
runId. Also fixes an incomplete mock response for the PR states polling
test.

Co-Authored-By: Claude <noreply@anthropic.com>
Comment thread static/app/views/seerExplorer/seerExplorerSessionContext.tsx
wrappedDispatch was a plain function recreated every render, causing
all dispatch context consumers to re-render unnecessarily and
cascading callback instability through useSeerExplorerDrawer and
useSeerExplorer.

Co-Authored-By: Claude <noreply@anthropic.com>
The sessions query is capped at 20 results. If the active run (from
sessionStorage or switchToRun) isn't in that window, conversations
never included it and runId silently resolved to null. Synthesize a
minimal entry so runId always resolves from reducer state.

Co-Authored-By: Claude <noreply@anthropic.com>
Comment thread static/app/views/seerExplorer/seerExplorerSessionContext.tsx
const [runId, setRunId] = useSeerExplorerRunId();
const {conversations} = useSeerExplorerSessions();
const dispatch = useSeerExplorerDispatch();
const runId = conversations.find(c => c.status === 'active')?.run_id ?? null;
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.

Repeated active run derivation across three files

Low Severity

The expression conversations.find(c => c.status === 'active')?.run_id ?? null is duplicated identically in topBar.tsx, useSeerExplorer.tsx, and useSeerExplorerContext.tsx. Since the conversations memo in the provider already computes activeId internally, exposing it as part of the context value (or as a small helper) would eliminate the redundancy and reduce the risk of these three sites drifting apart if the active-run logic changes.

Additional Locations (2)
Fix in Cursor Fix in Web

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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is ok.

Synthetic conversation entries (injected when active run is outside
the API's top N) have empty titles and timestamps, producing broken
menu items and Invalid Date in TimeSince.

Co-Authored-By: Claude <noreply@anthropic.com>
Runs can only be activated via the session menu or
openSeerExplorerDrawer, both sourced from query results. A persisted
runId not found in the session list is treated as invalid — runId
resolves to null and the user starts fresh.

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

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 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

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 9685060. Configure here.

...session,
status: state[session.run_id]?.status ?? ('idle' as const),
}));
}, [query.data?.data, state]);
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.

Duplicated active run derivation across three files

Low Severity

The expression conversations.find(c => c.status === 'active')?.run_id ?? null is duplicated in three separate files (topBar.tsx, useSeerExplorer.tsx, useSeerExplorerContext.tsx). Since the PR specifically centralizes session state into SeerExplorerSessionsProvider, the active run ID could be computed once inside the provider and exposed alongside conversations in the context value, eliminating the repeated derivation.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9685060. Configure here.

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.

1 participant