Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions dashboard/src/components/activity/ActivityTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ function resolveActivityActorFlow(item: LiveActivityItem): ActivityActorFlow {
executor,
mode: 'handoff',
primaryLabel: executor.label,
subtitle: `${requester.label} -> ${executor.label}`,
subtitle: `${requester.label} handed off to ${executor.label}`,
};
}

Expand All @@ -511,7 +511,7 @@ function resolveActivityActorFlow(item: LiveActivityItem): ActivityActorFlow {
executor: null,
mode: 'requested',
primaryLabel: requester.label,
subtitle: `${requester.label} requested dispatch`,
subtitle: `${requester.label} requested a run`,
};
}

Expand Down Expand Up @@ -3302,11 +3302,15 @@ export const ActivityTimeline = memo(function ActivityTimeline({
const [sentinelInView, setSentinelInView] = useState(false);
const pendingAutoExpandRef = useRef<ActivityTimeFilterId | null>(null);
const lastKnownFilterRef = useRef<ActivityTimeFilterId>(timeFilterId);
const manualFilterChangedAtRef = useRef<number>(Date.now());
const userHasScrolledRef = useRef(false);

useEffect(() => {
if (lastKnownFilterRef.current === timeFilterId) return;
lastKnownFilterRef.current = timeFilterId;
pendingAutoExpandRef.current = null;
manualFilterChangedAtRef.current = Date.now();
userHasScrolledRef.current = false;
}, [timeFilterId]);

useEffect(() => {
Expand All @@ -3326,7 +3330,14 @@ export const ActivityTimeline = memo(function ActivityTimeline({
);

observer.observe(target);
return () => observer.disconnect();
const handleScroll = () => {
userHasScrolledRef.current = true;
};
root.addEventListener('scroll', handleScroll, { passive: true });
return () => {
observer.disconnect();
root.removeEventListener('scroll', handleScroll);
};
// Intentionally stable deps — hasMore/isLoadingMore/onLoadMore read
// from refs so the observer doesn't get recreated on every poll cycle.
}, []);
Expand All @@ -3351,6 +3362,14 @@ export const ActivityTimeline = memo(function ActivityTimeline({
if (!sentinelInView) return;
if (hasMore || isLoadingMore) return;
if (!onTimeFilterChange) return;
// Don't auto-expand unless the user has actively scrolled — prevents
// the sentinel from firing on mount when the list is short (e.g.
// "Last hour" with few items) and silently overriding the chosen filter.
if (!userHasScrolledRef.current) return;
// Cooldown: don't auto-expand within 3s of a manual filter change to
// avoid overriding the user's explicit selection.
const msSinceManualChange = Date.now() - manualFilterChangedAtRef.current;
if (msSinceManualChange < 3_000) return;
const nextFilter = nextActivityTimeFilter(timeFilterId);
if (!nextFilter) return;
if (pendingAutoExpandRef.current === nextFilter) return;
Expand Down Expand Up @@ -4598,10 +4617,10 @@ export const ActivityTimeline = memo(function ActivityTimeline({
: 'border-white/[0.08] bg-white/[0.02] text-secondary hover:bg-white/[0.06] hover:text-primary'
)}
>
{showSyncEvents ? 'Hide sync replay events' : 'Show sync replay events'}
{showSyncEvents ? 'Hide background sync events' : 'Show background sync events'}
</button>
<p className="mt-1 text-micro leading-relaxed text-muted">
Changeset replay and outbox sync events are low-signal operational noise for most users.
Background sync events are routine system operations that most users can safely ignore.
</p>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1705,7 +1705,7 @@ function MissionControlInner({
disabled={
autopilotUnavailable ||
autopilot.isStarting ||
autopilot.isStopping
autopilot.isGracefullyStopping
}
title={
autopilotUnavailable
Expand Down Expand Up @@ -1773,7 +1773,9 @@ function MissionControlInner({
<span>
{autopilotNeedsUpgrade
? 'Upgrade Autopilot'
: `${autopilot.isRunning ? 'Stop' : 'Start'} Autopilot`}
: autopilot.isGracefullyStopping
? 'Stopping Autopilot…'
: `${autopilot.isRunning ? 'Stop' : 'Start'} Autopilot`}
</span>
{autopilot.isRunning && hasActiveRuntime && (
<span className="w-1.5 h-1.5 rounded-full bg-[#0AD4C4] status-breathe" />
Expand Down
5 changes: 4 additions & 1 deletion dashboard/src/hooks/useAutoContinue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,12 +285,15 @@ export function useAutoContinue({
});

const run = statusQuery.data?.run ?? null;
const isRunning = run?.status === 'running' || run?.status === 'stopping';
const runStatus = run?.status ?? null;
const isRunning = runStatus === 'running' || runStatus === 'stopping';
const isGracefullyStopping = runStatus === 'stopping' || stopMutation.isPending;

return {
status: statusQuery.data ?? null,
run,
isRunning,
isGracefullyStopping,
isLoading: statusQuery.isLoading,
error: statusQuery.data?.error ?? statusQuery.error?.message ?? null,
start: startMutation.mutateAsync,
Expand Down
Loading