Skip to content

feat(signals): make the scout suggestions wait honest and send cold projects to the chat #96342

Closed

Description

@andrewm4894

Context

#95462 put the "Suggested for this project" strip on the scouts tab behind the scouts-suggestions-ui flag. Its premise is that the batch is pre-computed by the scheduled producer (#85963), so there is nothing to wait for.

On a project the producer has not reached yet, the header "Suggest a scout" button breaks that premise. askForSuggestions in scoutSuggestionsLogic finds no picks and calls POST .../signals/scout/suggestions/refresh/, which dispatches a headless scan. The strip then shows three skeleton cards and polls the list every 15 seconds for up to an hour. The scan takes 2 to 5 minutes.

Dogfooding this on a never-scanned project was a bad experience:

  • Nothing on the page says a scan is running, how long it takes, or that leaving is fine. The only hints are a tooltip on the disabled Refresh button and a "Scanning the project…" line that appears only when the strip is collapsed.
  • The person's mental model comes from the older "Suggest a scout" chat task, where they watch the agent work. The cold-start path has the wait of a task with none of the visibility.
  • Skeletons read as a hang, so the natural reaction is to press again or reload. A reload sends a second refresh request, which lands as a 409 (harmless, the daily attempt is refunded, but it shows the guard only lives in memory).
  • When a batch does exist, pressing Refresh replaces the existing cards with skeletons, even though the previous picks stay valid while the scan runs.

The cold-start case shrinks as the producer drains its queue, so this should not turn into a big build. It should restore the strip's premise and make the one remaining wait honest.

Proposal

1. A cold press opens the chat task, not a headless scan

When the project has no picks (suggestions.length === 0 after the read settles), "Suggest a scout" in the header starts the existing author_scout chat task, the same entry the Ask menu already offers. The person watches the agent and decides in the conversation. Chat-sourced suggestions are not stored as a batch; the person is in the loop there, so that is fine.

The header button keeps its other job: reopening a strip that was closed with the cross.

askForSuggestions stops calling requestRefresh. The headless scan is reachable only from the strip's own Refresh button, which exists only when a batch is on screen.

2. Refresh keeps the current picks on screen and says what is happening

While a refresh runs:

  • The existing cards stay visible. They are the previous batch and still valid, and stale is already a footer note, not an error.
  • One status line replaces the skeletons, placed where the footer sits:
    • "Scanning this project for new picks. This usually takes a few minutes. You can leave this page and check back for the new suggestions."
    • Elapsed time after it, from the press: "Started 2 minutes ago."
  • The Refresh button stays disabled with the tooltip "Scanning. Check back in a few minutes."
  • On accept (202) or already running (409), one toast: "Scanning this project. Check back in a few minutes for new suggestions."

Polling stays as it is, so someone who does stay sees the new cards land. The existing end-of-scan toasts stay: "That scan found nothing new to suggest." and "That scan didn't finish. Try again in a moment."

Skeleton cards remain only for the first read of the list on mount, when there is nothing to show yet.

3. Refresh survives a reload

Persist the in-flight refresh start time per user and project alongside collapsedOverride and stripHidden (same buildUserScopedPersistenceConfig). On mount, if a start time is set and younger than the poll ceiling, resume the status line and polling instead of sending a second request. Clear it when the scan settles.

4. Telemetry

  • Scout suggestions refreshed gains source: 'strip_refresh'. The header button no longer produces this event.
  • A new Scout suggestions chat opened from the header cold press, with reason: 'no_picks', so the dogfood read can see how often projects are cold when someone asks.

Out of scope

  • Linking the status line to the running task. The runner already creates a TaskRun and its id is on the completed event, so a "watch it run" link is possible, but it needs a column on the row and an endpoint change. Revisit if the refresh telemetry shows people leaning on Refresh. It would also close the gap that failed runs lose their task_run_id on the event.
  • Producer prioritization from a press. It turns a bounded wait into an unbounded one.
  • Anything about the strip's collapsed-by-default posture.

Files

  • products/signals/frontend/inbox/logics/scoutSuggestionsLogic.ts: askForSuggestions, requestRefresh, startRefreshPolling, the isRefreshing reducer, persisted refresh start.
  • products/signals/frontend/inbox/components/config/scouts/ScoutSuggestionsStrip.tsx: StripBody keeps the grid during a refresh, status line, tooltip copy.
  • products/signals/frontend/inbox/components/config/scouts/ScoutsRosterActions.tsx: ShowSuggestionsButton opens the chat task on a cold project.
  • products/signals/frontend/inbox/inboxAnalytics.ts: the two telemetry changes.
  • products/signals/frontend/inbox/logics/scoutSuggestionsLogic.test.ts and the strip stories for the new state.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions