Repository navigation
Description
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 refreshedgainssource: 'strip_refresh'. The header button no longer produces this event.- A new
Scout suggestions chat openedfrom the header cold press, withreason: '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_idon 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, theisRefreshingreducer, persisted refresh start.products/signals/frontend/inbox/components/config/scouts/ScoutSuggestionsStrip.tsx:StripBodykeeps the grid during a refresh, status line, tooltip copy.products/signals/frontend/inbox/components/config/scouts/ScoutsRosterActions.tsx:ShowSuggestionsButtonopens the chat task on a cold project.products/signals/frontend/inbox/inboxAnalytics.ts: the two telemetry changes.products/signals/frontend/inbox/logics/scoutSuggestionsLogic.test.tsand the strip stories for the new state.
Activity