feat(KB-171): propagate availableModels prop through dashboard component tree
- Update App.tsx to fetch and pass availableModels to Board - Update Board component to accept and pass availableModels to Column - Update Column component to receive availableModels prop - Update ListView to handle availableModels prop - Update Board test mock to include availableModels prop
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
import type { TaskDetail, TaskCreateInput, Task, ThemeMode } from "@kb/core";
|
||||
import { fetchConfig, fetchSettings, fetchAuthStatus, updateSettings } from "./api";
|
||||
import { fetchConfig, fetchSettings, fetchAuthStatus, updateSettings, fetchModels } from "./api";
|
||||
import type { ModelInfo } from "./api";
|
||||
import { Header } from "./components/Header";
|
||||
import { Board } from "./components/Board";
|
||||
import { ListView } from "./components/ListView";
|
||||
@@ -49,6 +50,7 @@ function AppInner() {
|
||||
});
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [githubTokenConfigured, setGithubTokenConfigured] = useState(false);
|
||||
const [availableModels, setAvailableModels] = useState<ModelInfo[]>([]);
|
||||
const { tasks, createTask, moveTask, deleteTask, mergeTask, retryTask, updateTask, duplicateTask, archiveTask, unarchiveTask, archiveAllDone } = useTasks();
|
||||
|
||||
// Theme management
|
||||
@@ -86,6 +88,13 @@ function AppInner() {
|
||||
})
|
||||
.catch(() => {/* fail silently — do not auto-open */});
|
||||
}, []);
|
||||
|
||||
// Fetch available models
|
||||
useEffect(() => {
|
||||
fetchModels()
|
||||
.then((models) => setAvailableModels(models))
|
||||
.catch(() => {/* keep empty array on failure */});
|
||||
}, []);
|
||||
const { toasts, addToast, removeToast } = useToast();
|
||||
|
||||
// Persist view preference to localStorage
|
||||
@@ -230,6 +239,7 @@ function AppInner() {
|
||||
onUnarchiveTask={unarchiveTask}
|
||||
onArchiveAllDone={archiveAllDone}
|
||||
searchQuery={searchQuery}
|
||||
availableModels={availableModels}
|
||||
/>
|
||||
) : (
|
||||
// List view now uses the same modal-based create flow as board view.
|
||||
@@ -241,6 +251,7 @@ function AppInner() {
|
||||
globalPaused={globalPaused}
|
||||
onNewTask={handleNewTaskOpen}
|
||||
onQuickCreate={handleBoardQuickCreate}
|
||||
availableModels={availableModels}
|
||||
/>
|
||||
)}
|
||||
{detailTask && (
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Column } from "./Column";
|
||||
import type { ToastType } from "../hooks/useToast";
|
||||
import { useState, useMemo, useEffect, useCallback, useRef } from "react";
|
||||
import { useBatchBadgeFetch } from "../hooks/useBatchBadgeFetch";
|
||||
import type { ModelInfo } from "../api";
|
||||
|
||||
interface BoardProps {
|
||||
tasks: Task[];
|
||||
@@ -24,6 +25,7 @@ interface BoardProps {
|
||||
onUnarchiveTask?: (id: string) => Promise<Task>;
|
||||
onArchiveAllDone?: () => Promise<Task[]>;
|
||||
searchQuery?: string;
|
||||
availableModels?: ModelInfo[];
|
||||
}
|
||||
|
||||
function sortTasksForColumn(tasks: Task[]): Task[] {
|
||||
@@ -42,7 +44,7 @@ function areTaskArraysEqual(previous: Task[], next: Task[]): boolean {
|
||||
return previous.every((task, index) => task === next[index]);
|
||||
}
|
||||
|
||||
export function Board({ tasks, maxConcurrent, onMoveTask, onOpenDetail, addToast, onQuickCreate, onNewTask, autoMerge, onToggleAutoMerge, globalPaused, onUpdateTask, onArchiveTask, onUnarchiveTask, onArchiveAllDone, searchQuery = "" }: BoardProps) {
|
||||
export function Board({ tasks, maxConcurrent, onMoveTask, onOpenDetail, addToast, onQuickCreate, onNewTask, autoMerge, onToggleAutoMerge, globalPaused, onUpdateTask, onArchiveTask, onUnarchiveTask, onArchiveAllDone, searchQuery = "", availableModels }: BoardProps) {
|
||||
const [archivedCollapsed, setArchivedCollapsed] = useState(true);
|
||||
const { fetchBatch } = useBatchBadgeFetch();
|
||||
const debounceTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
@@ -147,6 +149,7 @@ export function Board({ tasks, maxConcurrent, onMoveTask, onOpenDetail, addToast
|
||||
onArchiveTask={onArchiveTask}
|
||||
onUnarchiveTask={onUnarchiveTask}
|
||||
allTasks={filteredTasks}
|
||||
availableModels={availableModels}
|
||||
{...(col === "triage" ? { onQuickCreate, onNewTask } : {})}
|
||||
{...(col === "in-review" ? { autoMerge, onToggleAutoMerge } : {})}
|
||||
{...(col === "done" ? { onArchiveAllDone } : {})}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { QuickEntryBox } from "./QuickEntryBox";
|
||||
import { groupByWorktree } from "../utils/worktreeGrouping";
|
||||
import type { ToastType } from "../hooks/useToast";
|
||||
import { ChevronDown, ChevronUp, Archive } from "lucide-react";
|
||||
import type { ModelInfo } from "../api";
|
||||
|
||||
const PAGINATED_COLUMN_THRESHOLD = 100;
|
||||
const VISIBLE_TASKS_INITIAL = 50;
|
||||
@@ -35,9 +36,10 @@ interface ColumnProps {
|
||||
collapsed?: boolean;
|
||||
onToggleCollapse?: () => void;
|
||||
allTasks?: Task[];
|
||||
availableModels?: ModelInfo[];
|
||||
}
|
||||
|
||||
function ColumnComponent({ column, tasks, maxConcurrent, onMoveTask, onOpenDetail, addToast, onQuickCreate, onNewTask, autoMerge, onToggleAutoMerge, globalPaused, onUpdateTask, onArchiveTask, onUnarchiveTask, onArchiveAllDone, collapsed, onToggleCollapse, allTasks }: ColumnProps) {
|
||||
function ColumnComponent({ column, tasks, maxConcurrent, onMoveTask, onOpenDetail, addToast, onQuickCreate, onNewTask, autoMerge, onToggleAutoMerge, globalPaused, onUpdateTask, onArchiveTask, onUnarchiveTask, onArchiveAllDone, collapsed, onToggleCollapse, allTasks, availableModels }: ColumnProps) {
|
||||
const [dragOver, setDragOver] = useState(false);
|
||||
const [visibleTaskCount, setVisibleTaskCount] = useState(VISIBLE_TASKS_INITIAL);
|
||||
const countFlashing = useFlashOnIncrease(tasks.length);
|
||||
@@ -174,6 +176,7 @@ function ColumnComponent({ column, tasks, maxConcurrent, onMoveTask, onOpenDetai
|
||||
onCreate={onQuickCreate}
|
||||
addToast={addToast}
|
||||
tasks={allTasks ?? []}
|
||||
availableModels={availableModels}
|
||||
/>
|
||||
)}
|
||||
{column === "in-progress" ? (
|
||||
|
||||
@@ -3,6 +3,7 @@ import { LayoutGrid, List as ListIcon, ArrowUpDown, ArrowUp, ArrowDown, Search,
|
||||
import type { Task, TaskDetail, Column, TaskStep, TaskCreateInput } from "@kb/core";
|
||||
import { COLUMN_LABELS, COLUMNS } from "@kb/core";
|
||||
import { fetchTaskDetail } from "../api";
|
||||
import type { ModelInfo } from "../api";
|
||||
import { QuickEntryBox } from "./QuickEntryBox";
|
||||
import type { ToastType } from "../hooks/useToast";
|
||||
|
||||
@@ -32,6 +33,7 @@ interface ListViewProps {
|
||||
globalPaused?: boolean;
|
||||
onNewTask?: () => void;
|
||||
onQuickCreate?: (input: TaskCreateInput) => Promise<void>;
|
||||
availableModels?: ModelInfo[];
|
||||
}
|
||||
|
||||
function getStepProgress(steps: TaskStep[]): string {
|
||||
@@ -54,6 +56,7 @@ export function ListView({
|
||||
globalPaused,
|
||||
onNewTask,
|
||||
onQuickCreate,
|
||||
availableModels,
|
||||
}: ListViewProps) {
|
||||
const [sortField, setSortField] = useState<SortField>("id");
|
||||
const [sortDirection, setSortDirection] = useState<SortDirection>("desc");
|
||||
@@ -390,7 +393,9 @@ export function ListView({
|
||||
<div className="list-quick-entry">
|
||||
<QuickEntryBox
|
||||
onCreate={onQuickCreate ?? (async () => addToast("Task creation not available", "error"))}
|
||||
addToast={addToast}
|
||||
addToast={addToast}
|
||||
tasks={tasks}
|
||||
availableModels={availableModels}
|
||||
/>
|
||||
</div>
|
||||
<div className="list-column-toggle" ref={columnDropdownRef}>
|
||||
|
||||
@@ -10,7 +10,7 @@ const columnRenderCounts: Record<string, number> = {};
|
||||
|
||||
// Mock child components so we only test Board's own rendering
|
||||
vi.mock("../Column", () => ({
|
||||
Column: React.memo(({ column, tasks, onToggleCollapse }: { column: string; tasks: Task[]; onToggleCollapse?: () => void }) => {
|
||||
Column: React.memo(({ column, tasks, onToggleCollapse, availableModels }: { column: string; tasks: Task[]; onToggleCollapse?: () => void; availableModels?: unknown }) => {
|
||||
columnRenderCounts[column] = (columnRenderCounts[column] ?? 0) + 1;
|
||||
return (
|
||||
<div data-testid={`column-${column}`} data-tasks={JSON.stringify(tasks)}>
|
||||
|
||||
Reference in New Issue
Block a user