Prevent <TasksLayout/> component from being unmounted when using (#1391)

keyboard shortcuts to navigate the kanban board.

In kanban boards with many tasks in a single column, when we use
keyboard shortcuts to select a task 'below the fold', the animation
restarts from the top of the kanban board, rather than preserving the
existing position.
This commit is contained in:
Britannio Jarrett
2025-11-27 18:41:15 +00:00
committed by GitHub
parent 34236c9572
commit 770d897403
4 changed files with 47 additions and 38 deletions

View File

@@ -20,7 +20,7 @@ const ExecutionProcessesContext =
createContext<ExecutionProcessesContextType | null>(null); createContext<ExecutionProcessesContextType | null>(null);
export const ExecutionProcessesProvider: React.FC<{ export const ExecutionProcessesProvider: React.FC<{
attemptId: string; attemptId: string | undefined;
children: React.ReactNode; children: React.ReactNode;
}> = ({ attemptId, children }) => { }> = ({ attemptId, children }) => {
const { const {

View File

@@ -1,5 +1,11 @@
import { SplitSide } from '@git-diff-view/react'; import { SplitSide } from '@git-diff-view/react';
import { createContext, useContext, useState, ReactNode } from 'react'; import {
createContext,
useContext,
useState,
ReactNode,
useEffect,
} from 'react';
import { genId } from '@/utils/id'; import { genId } from '@/utils/id';
export interface ReviewComment { export interface ReviewComment {
@@ -40,10 +46,20 @@ export function useReview() {
return context; return context;
} }
export function ReviewProvider({ children }: { children: ReactNode }) { export function ReviewProvider({
children,
attemptId,
}: {
children: ReactNode;
attemptId?: string;
}) {
const [comments, setComments] = useState<ReviewComment[]>([]); const [comments, setComments] = useState<ReviewComment[]>([]);
const [drafts, setDrafts] = useState<Record<string, ReviewDraft>>({}); const [drafts, setDrafts] = useState<Record<string, ReviewDraft>>({});
useEffect(() => {
return () => clearComments();
}, [attemptId]);
const addComment = (comment: Omit<ReviewComment, 'id'>) => { const addComment = (comment: Omit<ReviewComment, 'id'>) => {
const newComment: ReviewComment = { const newComment: ReviewComment = {
...comment, ...comment,

View File

@@ -21,15 +21,19 @@ interface UseExecutionProcessesResult {
* Live updates arrive at /execution_processes/<id> via add/replace/remove operations. * Live updates arrive at /execution_processes/<id> via add/replace/remove operations.
*/ */
export const useExecutionProcesses = ( export const useExecutionProcesses = (
taskAttemptId: string, taskAttemptId: string | undefined,
opts?: { showSoftDeleted?: boolean } opts?: { showSoftDeleted?: boolean }
): UseExecutionProcessesResult => { ): UseExecutionProcessesResult => {
const showSoftDeleted = opts?.showSoftDeleted; const showSoftDeleted = opts?.showSoftDeleted;
let endpoint: string | undefined;
if (taskAttemptId) {
const params = new URLSearchParams({ task_attempt_id: taskAttemptId }); const params = new URLSearchParams({ task_attempt_id: taskAttemptId });
if (typeof showSoftDeleted === 'boolean') { if (typeof showSoftDeleted === 'boolean') {
params.set('show_soft_deleted', String(showSoftDeleted)); params.set('show_soft_deleted', String(showSoftDeleted));
} }
const endpoint = `/api/execution-processes/stream/ws?${params.toString()}`; endpoint = `/api/execution-processes/stream/ws?${params.toString()}`;
}
const initialData = useCallback( const initialData = useCallback(
(): ExecutionProcessState => ({ execution_processes: {} }), (): ExecutionProcessState => ({ execution_processes: {} }),

View File

@@ -1029,12 +1029,11 @@ export function ProjectTasks() {
const effectiveMode: LayoutMode = selectedSharedTask ? null : mode; const effectiveMode: LayoutMode = selectedSharedTask ? null : mode;
const attemptArea = const attemptArea = (
attempt && selectedTask ? ( <GitOperationsProvider attemptId={attempt?.id}>
<GitOperationsProvider attemptId={attempt.id}>
<ClickedElementsProvider attempt={attempt}> <ClickedElementsProvider attempt={attempt}>
<ReviewProvider key={attempt.id}> <ReviewProvider attemptId={attempt?.id}>
<ExecutionProcessesProvider key={attempt.id} attemptId={attempt.id}> <ExecutionProcessesProvider attemptId={attempt?.id}>
<TasksLayout <TasksLayout
kanban={kanbanContent} kanban={kanbanContent}
attempt={attemptContent} attempt={attemptContent}
@@ -1048,16 +1047,6 @@ export function ProjectTasks() {
</ReviewProvider> </ReviewProvider>
</ClickedElementsProvider> </ClickedElementsProvider>
</GitOperationsProvider> </GitOperationsProvider>
) : (
<TasksLayout
kanban={kanbanContent}
attempt={attemptContent}
aux={auxContent}
isPanelOpen={isPanelOpen}
mode={effectiveMode}
isMobile={isMobile}
rightHeader={rightHeader}
/>
); );
return ( return (