From d75c21498992a5ab03fef786fec60a8318443454 Mon Sep 17 00:00:00 2001 From: szymeo <11583029+szymeo@users.noreply.github.com> Date: Sun, 31 May 2026 14:21:12 +0200 Subject: [PATCH 1/3] feat: smoother suggested-tasks list animation - page navigation slides horizontally with mode="wait" so the panel no longer jumps from two pages briefly stacking in the layout - dropped overflow-hidden so sliding rows are no longer clipped at the edges - card dismiss scales down and fades with a synced layout shift - center the task composer and anchor the suggestions panel below it - add a dev-only suggestion simulation harness gated by VITE_SIMULATE_SUGGESTIONS / a localStorage flag --- .../features/setup/hooks/useSetupDiscovery.ts | 13 ++++ .../setup/services/setupRunService.ts | 69 +++++++++++++++++++ .../features/setup/stores/setupStore.ts | 16 +++++ .../components/SuggestedTaskCard.tsx | 20 ++---- .../components/SuggestedTasksPanel.tsx | 53 +++++++------- .../task-detail/components/TaskInput.tsx | 22 +++--- apps/code/src/vite-env.d.ts | 1 + 7 files changed, 147 insertions(+), 47 deletions(-) diff --git a/apps/code/src/renderer/features/setup/hooks/useSetupDiscovery.ts b/apps/code/src/renderer/features/setup/hooks/useSetupDiscovery.ts index 4919da33ab..48e7c574b0 100644 --- a/apps/code/src/renderer/features/setup/hooks/useSetupDiscovery.ts +++ b/apps/code/src/renderer/features/setup/hooks/useSetupDiscovery.ts @@ -5,6 +5,14 @@ import { RENDERER_TOKENS } from "@renderer/di/tokens"; import { useActiveRepoStore } from "@stores/activeRepoStore"; import { useEffect } from "react"; +const SIMULATE_SUGGESTIONS_STORAGE_KEY = "posthog-code:simulate-suggestions"; + +function shouldSimulateSuggestions(): boolean { + if (!import.meta.env.DEV) return false; + if (import.meta.env.VITE_SIMULATE_SUGGESTIONS === "1") return true; + return window.localStorage.getItem(SIMULATE_SUGGESTIONS_STORAGE_KEY) === "1"; +} + export function useSetupDiscovery() { const selectedDirectory = useActiveRepoStore((s) => s.path); @@ -16,6 +24,11 @@ export function useSetupDiscovery() { useEffect(() => { if (!selectedDirectory) return; const service = get(RENDERER_TOKENS.SetupRunService); + if (shouldSimulateSuggestions()) { + service.startSuggestionSimulation(selectedDirectory); + return; + } + const discoveryEverStarted = Object.values( useSetupStore.getState().discoveryByRepo, ).some((d) => d.status !== "idle"); diff --git a/apps/code/src/renderer/features/setup/services/setupRunService.ts b/apps/code/src/renderer/features/setup/services/setupRunService.ts index eda36203ea..0259e8166a 100644 --- a/apps/code/src/renderer/features/setup/services/setupRunService.ts +++ b/apps/code/src/renderer/features/setup/services/setupRunService.ts @@ -28,6 +28,42 @@ const log = logger.scope("setup-run-service"); let activityIdCounter = 0; +const SIMULATED_SUGGESTION_CATEGORIES: DiscoveredTask["category"][] = [ + "bug", + "performance", + "dead_code", + "duplication", + "event_tracking", + "experiment", +]; + +const SIMULATED_SUGGESTION_INTERVAL_MS = 2500; + +function buildSimulatedSuggestion( + repoPath: string, + index: number, +): DiscoveredTask { + const category = + SIMULATED_SUGGESTION_CATEGORIES[ + index % SIMULATED_SUGGESTION_CATEGORIES.length + ]; + + return { + id: `simulated-suggestion-${index}`, + source: "agent", + repoPath, + category, + title: `Simulated suggestion ${index + 1}`, + description: + "A generated setup suggestion for exercising empty-state layout stability while new content keeps arriving.", + impact: + "This is dev-only test data for checking whether the composer and suggestion rows stay anchored during incremental updates.", + recommendation: + "Use this simulation to hover and click suggestions while the list keeps changing underneath the same UI surface.", + prompt: `Investigate simulated suggestion ${index + 1}`, + }; +} + function extractPathFromRawInput( tool: string, rawInput: Record | undefined, @@ -236,6 +272,9 @@ export class SetupRunService { private anyDiscoveryEverLaunched = false; private discoveryStartingByRepo = new Set(); private enricherSuggestionsRunningByRepo = new Set(); + private simulatedSuggestionsRepo: string | null = null; + private simulatedSuggestionsTimer: number | null = null; + private simulatedSuggestionsCount = 0; startSetup(directory: string): void { // Defense in depth: never auto-run from a non-idle persisted state. @@ -256,6 +295,36 @@ export class SetupRunService { this.injectEnricherSuggestions(directory); } + startSuggestionSimulation(directory: string): void { + if (!directory) return; + if (this.simulatedSuggestionsRepo === directory) return; + + if (this.simulatedSuggestionsTimer !== null) { + window.clearInterval(this.simulatedSuggestionsTimer); + } + + this.simulatedSuggestionsRepo = directory; + this.simulatedSuggestionsCount = 0; + useSetupStore + .getState() + .startDiscovery(directory, "simulated-discovery", "simulated-run"); + + const addNextSuggestion = () => { + useSetupStore + .getState() + .addDiscoveredTaskIfMissing( + buildSimulatedSuggestion(directory, this.simulatedSuggestionsCount), + ); + this.simulatedSuggestionsCount += 1; + }; + + addNextSuggestion(); + this.simulatedSuggestionsTimer = window.setInterval( + addNextSuggestion, + SIMULATED_SUGGESTION_INTERVAL_MS, + ); + } + startDiscovery(directory: string): void { if (!directory) return; if (this.anyDiscoveryEverLaunched) return; diff --git a/apps/code/src/renderer/features/setup/stores/setupStore.ts b/apps/code/src/renderer/features/setup/stores/setupStore.ts index 4614575e2a..124ce52f87 100644 --- a/apps/code/src/renderer/features/setup/stores/setupStore.ts +++ b/apps/code/src/renderer/features/setup/stores/setupStore.ts @@ -65,6 +65,7 @@ interface SetupStoreActions { completeEnrichment: (repoPath: string) => void; failEnrichment: (repoPath: string) => void; removeDiscoveredTask: (taskId: string, repoPath: string | null) => void; + addDiscoveredTaskIfMissing: (task: DiscoveredTask) => void; addEnricherSuggestionIfMissing: (task: DiscoveredTask) => void; pushDiscoveryActivity: (repoPath: string, entry: ActivityEntry) => void; resetSetup: () => void; @@ -263,6 +264,21 @@ export const useSetupStore = create()( })); }, + addDiscoveredTaskIfMissing: (task) => { + set((state) => { + if ( + state.discoveredTasks.some( + (t) => t.id === task.id && t.repoPath === task.repoPath, + ) + ) { + return state; + } + return { + discoveredTasks: [...state.discoveredTasks, task], + }; + }); + }, + // Adds an enricher-source suggestion if there isn't already one with // the same id+repoPath. Idempotent — safe to call repeatedly on every // detection run. Dismissed suggestions stay dismissed until `resetSetup`. diff --git a/apps/code/src/renderer/features/task-detail/components/SuggestedTaskCard.tsx b/apps/code/src/renderer/features/task-detail/components/SuggestedTaskCard.tsx index c3785c6e0e..dc770e3032 100644 --- a/apps/code/src/renderer/features/task-detail/components/SuggestedTaskCard.tsx +++ b/apps/code/src/renderer/features/task-detail/components/SuggestedTaskCard.tsx @@ -9,14 +9,12 @@ import { motion } from "framer-motion"; export interface SuggestedTaskCardProps { task: DiscoveredTask; - index: number; onSelect: (task: DiscoveredTask) => void; onDismiss: (task: DiscoveredTask) => void; } export function SuggestedTaskCard({ task, - index, onSelect, onDismiss, }: SuggestedTaskCardProps) { @@ -26,19 +24,15 @@ export function SuggestedTaskCard({ return (