Drag-and-Drop Kanban
This is the feature that transforms the app from a task list into a real kanban board. We're using @dnd-kit — it's the modern React DnD library with the best TypeScript support and accessibility built in.
Add drag-and-drop to the Taskflow kanban board using @dnd-kit/core and @dnd-kit/sortable.
Here are the existing components: [paste KanbanBoard.tsx, Column.tsx, TaskCard.tsx]
Here's the project store: [paste useProjectStore.ts, specifically the moveTask action]
Requirements:
- Drag tasks between columns (change status)
- Drag tasks within a column (reorder by position)
- Calculate position using float midpoint: between tasks at 1.0 and 2.0, insert at 1.5. End of column = max + 1.
- Optimistic update: move the card immediately in the UI, then call the API. If API fails, revert.
- Visual feedback: dragging card gets slight opacity + shadow, drop zone highlighted
- Keep the existing component structure, just wrap with DnD providers
The prompt is specific about the position calculation and optimistic updates — these are the two areas where AI needs the most guidance. Here's the core logic:
function calculatePosition(
tasks: Task[],
overIndex: number
): number {
// Dropping at the end of column
if (overIndex >= tasks.length) {
const last = tasks[tasks.length - 1];
return last ? last.position + 1 : 1;
}
// Dropping at the beginning
if (overIndex === 0) {
return tasks[0].position / 2;
}
// Dropping between two tasks
const before = tasks[overIndex - 1];
const after = tasks[overIndex];
return (before.position + after.position) / 2;
}
And the optimistic update pattern in the drag end handler:
function handleDragEnd(event: DragEndEvent) {
const { active, over } = event;
if (!over) return;
const taskId = active.id as string;
const newStatus = over.data.current?.status as TaskStatus;
const overIndex = over.data.current?.index as number;
// Get tasks in the target column
const columnTasks = tasks
.filter((t) => t.status === newStatus && t.id !== taskId)
.sort((a, b) => a.position - b.position);
const newPosition = calculatePosition(columnTasks, overIndex);
// Optimistic update: move immediately in local state
useProjectStore.getState().optimisticMoveTask(taskId, newStatus, newPosition);
// Then sync with API (revert on failure)
useProjectStore.getState().moveTask(taskId, newStatus, newPosition)
.catch(() => {
// Revert: re-fetch tasks from server
useProjectStore.getState().fetchTasks(activeProjectId!);
});
}
Pro Tip: Optimistic Updates
The optimistic update is what makes the UI feel instant. The card moves immediately when you drop it — no waiting for the API. If the API call fails, we revert by re-fetching from the server. This pattern requires adding an optimisticMoveTask action to the Zustand store that updates local state without an API call. Ask AI to add it — it's a 10-line function.
Filtering & Search
Build a FilterBar component for the Taskflow kanban board.
Current components: [paste KanbanBoard.tsx]
Types: [paste types.ts]
The FilterBar sits above the kanban columns and provides:
- Text search (filters tasks by title, case-insensitive)
- Priority filter (toggle buttons: All, Low, Medium, High, Urgent)
- Assignee filter (text input, filters by assignee field)
- Active filter count badge
Implementation:
- Store filter state locally in FilterBar with useState (not Zustand — these are ephemeral UI state)
- Pass a filterFn callback to KanbanBoard that filters the task array
- Debounce the text search input (300ms)
- "Clear all" button when any filter is active
Tailwind. Compact single row. Dark theme.
// FilterBar passes this to KanbanBoard
const filterTasks = useCallback(
(tasks: Task[]) => {
return tasks.filter((task) => {
// Text search
if (searchQuery && !task.title.toLowerCase().includes(searchQuery.toLowerCase())) {
return false;
}
// Priority filter
if (priorityFilter && task.priority !== priorityFilter) {
return false;
}
// Assignee filter
if (assigneeFilter && task.assignee?.toLowerCase() !== assigneeFilter.toLowerCase()) {
return false;
}
return true;
});
},
[searchQuery, priorityFilter, assigneeFilter]
);
// KanbanBoard applies it before distributing to columns
const filteredTasks = filterFn ? filterFn(tasks) : tasks;
Filters are kept in local component state, not Zustand. Why? Because they're ephemeral — they reset when you switch projects, they're not shared across components, and they don't need persistence. Not everything belongs in a global store. This distinction matters for keeping your architecture clean.
Toast Notifications
Every successful action and every error needs feedback. Without toasts, the user creates a task and has no confirmation it worked. Without error toasts, API failures are silent.
Build a minimal toast notification system for Taskflow.
Requirements:
- useToast hook that returns toast.success(message) and toast.error(message)
- Toasts appear in bottom-right corner, stack vertically
- Auto-dismiss after 3 seconds (success) or 5 seconds (error)
- Slide-in animation from the right
- Green accent for success, red for error
- Small Zustand store for toast state
- ToastContainer component added once in App.tsx
Keep it minimal — no library, just Zustand + Tailwind + a bit of CSS animation. Under 80 lines total.
import { create } from 'zustand';
interface Toast {
id: string;
message: string;
type: 'success' | 'error';
}
interface ToastState {
toasts: Toast[];
success: (message: string) => void;
error: (message: string) => void;
dismiss: (id: string) => void;
}
export const useToast = create<ToastState>((set) => ({
toasts: [],
success: (message) => {
const id = crypto.randomUUID();
set((s) => ({ toasts: [...s.toasts, { id, message, type: 'success' }] }));
setTimeout(() => set((s) => ({ toasts: s.toasts.filter((t) => t.id !== id) })), 3000);
},
error: (message) => {
const id = crypto.randomUUID();
set((s) => ({ toasts: [...s.toasts, { id, message, type: 'error' }] }));
setTimeout(() => set((s) => ({ toasts: s.toasts.filter((t) => t.id !== id) })), 5000);
},
dismiss: (id) => set((s) => ({ toasts: s.toasts.filter((t) => t.id !== id) })),
}));
Then add toast.success('Task created') after every successful store action and toast.error(err.message) in every catch block. One pass through the codebase, five minutes of work.
Responsive Design
Make the Taskflow dashboard responsive.
Current layout: [paste DashboardLayout.tsx, Sidebar.tsx, KanbanBoard.tsx]
Breakpoints:
- Desktop (=1024px): sidebar + kanban as-is
- Tablet (768—1023px): collapsible sidebar (hamburger icon), kanban columns 2—2 grid
- Mobile (<768px): sidebar as full-screen overlay, kanban as single scrollable column with status headers
Implementation:
- Add a sidebarOpen state to DashboardLayout
- Sidebar gets a backdrop overlay on mobile
- KanbanBoard switches layout via Tailwind responsive classes
- TaskCard stays the same across all breakpoints
- FilterBar stacks vertically on mobile
The responsive prompt is detailed because AI tends to be lazy with mobile layouts — it'll add one md: class and call it done. By specifying the exact layout per breakpoint, you get a genuinely usable mobile experience.
// KanbanBoard responsive layout
<div className={`
grid gap-4 p-4 h-full
grid-cols-1 overflow-y-auto
md:grid-cols-2 md:overflow-y-auto
lg:grid-cols-4 lg:overflow-x-auto lg:overflow-y-hidden
`}>
{COLUMNS.map((col) => (
<Column key={col.status} {...col} tasks={columnTasks} />
))}
</div>
Pro Tip: Test on Real Devices
Open http://your-local-ip:5173 on your phone while developing. Tailwind's responsive classes look correct in Chrome DevTools but feel different on an actual touch screen. Two minutes of real-device testing catches layout issues that the browser emulator misses — especially with the sidebar overlay and kanban scrolling.
Quick Wins
With the core features done, spend 15 minutes on quick wins that make the app feel polished. Each of these is a single focused prompt:
- Empty states — "No tasks yet" message in empty columns with a subtle illustration and a "Create your first task" CTA button
- Loading skeletons — Pulse animations on task cards while data loads, instead of a spinner
- Keyboard shortcuts —
Nto create task,Escto close modal (modal already has this),/to focus search - Due date highlighting — Red text if overdue, amber if due today, muted if future
- Confirm dialogs — "Are you sure?" before archiving a project or deleting a task
Add due date highlighting to TaskCard. Here's the current component: [paste TaskCard.tsx]
Rules: if the due date has passed, show it in red (text-red-400). If it's today, show amber (text-amber-400). If it's in the future, show muted (text-gray-500). If no due date, don't render the date element at all.
Each quick win is a 2-minute prompt and a 1-minute review. Five quick wins = 15 minutes total. The cumulative effect is significant — the app goes from "functional prototype" to "something I'd actually use."
The Refactor Pause
We're now 7-8 hours into the build with a lot of code accumulated. Before moving to testing and deployment, it's time for the refactor step from the manual.
Review all the frontend code we've built for Taskflow.
[paste all component files, stores, and utilities]
A senior developer reviewing this codebase would improve:
1. Functions that have grown too long
2. Duplicated logic that should be extracted
3. Components with too many responsibilities
4. Inconsistent patterns between similar components
5. Missing TypeScript types (any accidental any types?)
Identify the top 5 issues. Fix them. Leave the rest — we ship tonight.
Top 5 issues:
1. Duplicated API error handling — Three components have identical try/catch/toast patterns. Extract a withToast wrapper function.
2. KanbanBoard is 180 lines — The drag handler logic should be extracted into a useDragAndDrop custom hook.
3. Date formatting repeated — TaskCard and TaskModal both format dates differently. Extract a formatDate utility.
4. Magic strings — Column statuses and priorities are hardcoded in multiple places. Use the COLUMNS config array everywhere.
5. Missing types on event handlers — Two onChange handlers are typed as any. Fixed: React.ChangeEvent<HTMLInputElement>.
"Fix top 5, leave the rest" is the disciplined constraint. You could spend 2 hours refactoring, but you'd miss your deployment window. Fix what matters, ship the rest, improve later.
Part 4 Complete — Polished App
- Drag-and-drop kanban with @dnd-kit, float position calculation, and optimistic updates
- FilterBar with text search (debounced), priority toggles, and assignee filter
- Toast notification system — success and error feedback on every action
- Responsive design: desktop 4-column, tablet 2-column, mobile single-column with overlay sidebar
- Quick wins: empty states, loading skeletons, keyboard shortcuts, due date highlighting, confirm dialogs
- Refactor pass: top 5 issues identified and fixed, code quality improved before shipping