Rebuild of the kanban scaling logic, and adding constraints to window scaling logic for electron and web

This commit is contained in:
trueheads
2025-12-21 16:47:21 -06:00
parent ee9ccd03d6
commit 9beefd1ac3
10 changed files with 1918 additions and 266 deletions

View File

@@ -1,8 +1,7 @@
import { memo } from "react";
import { useDroppable } from "@dnd-kit/core";
import { cn } from "@/lib/utils";
import type { ReactNode } from "react";
import { memo } from 'react';
import { useDroppable } from '@dnd-kit/core';
import { cn } from '@/lib/utils';
import type { ReactNode } from 'react';
interface KanbanColumnProps {
id: string;
@@ -39,10 +38,12 @@ export const KanbanColumn = memo(function KanbanColumn({
<div
ref={setNodeRef}
className={cn(
"relative flex flex-col h-full rounded-xl transition-all duration-200",
!width && "w-72", // Only apply w-72 if no custom width
showBorder && "border border-border/60",
isOver && "ring-2 ring-primary/30 ring-offset-1 ring-offset-background"
'relative flex flex-col h-full rounded-xl',
// Only transition ring/shadow for drag-over effect, not width
'transition-[box-shadow,ring] duration-200',
!width && 'w-72', // Only apply w-72 if no custom width
showBorder && 'border border-border/60',
isOver && 'ring-2 ring-primary/30 ring-offset-1 ring-offset-background'
)}
style={widthStyle}
data-testid={`kanban-column-${id}`}
@@ -50,8 +51,8 @@ export const KanbanColumn = memo(function KanbanColumn({
{/* Background layer with opacity */}
<div
className={cn(
"absolute inset-0 rounded-xl backdrop-blur-sm transition-colors duration-200",
isOver ? "bg-accent/80" : "bg-card/80"
'absolute inset-0 rounded-xl backdrop-blur-sm transition-colors duration-200',
isOver ? 'bg-accent/80' : 'bg-card/80'
)}
style={{ opacity: opacity / 100 }}
/>
@@ -59,11 +60,11 @@ export const KanbanColumn = memo(function KanbanColumn({
{/* Column Header */}
<div
className={cn(
"relative z-10 flex items-center gap-3 px-3 py-2.5",
showBorder && "border-b border-border/40"
'relative z-10 flex items-center gap-3 px-3 py-2.5',
showBorder && 'border-b border-border/40'
)}
>
<div className={cn("w-2.5 h-2.5 rounded-full shrink-0", colorClass)} />
<div className={cn('w-2.5 h-2.5 rounded-full shrink-0', colorClass)} />
<h3 className="font-semibold text-sm text-foreground/90 flex-1 tracking-tight">{title}</h3>
{headerAction}
<span className="text-xs font-medium text-muted-foreground/80 bg-muted/50 px-2 py-0.5 rounded-md tabular-nums">
@@ -74,11 +75,11 @@ export const KanbanColumn = memo(function KanbanColumn({
{/* Column Content */}
<div
className={cn(
"relative z-10 flex-1 overflow-y-auto p-2 space-y-2.5",
'relative z-10 flex-1 overflow-y-auto p-2 space-y-2.5',
hideScrollbar &&
"[&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]",
'[&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]',
// Smooth scrolling
"scroll-smooth"
'scroll-smooth'
)}
>
{children}

View File

@@ -1,22 +1,15 @@
import {
DndContext,
DragOverlay,
} from "@dnd-kit/core";
import {
SortableContext,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { Card, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { HotkeyButton } from "@/components/ui/hotkey-button";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { KanbanColumn, KanbanCard } from "./components";
import { Feature } from "@/store/app-store";
import { FastForward, Lightbulb, Archive } from "lucide-react";
import { useKeyboardShortcutsConfig } from "@/hooks/use-keyboard-shortcuts";
import { useResponsiveKanban } from "@/hooks/use-responsive-kanban";
import { COLUMNS, ColumnId } from "./constants";
import { DndContext, DragOverlay } from '@dnd-kit/core';
import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { Card, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { HotkeyButton } from '@/components/ui/hotkey-button';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { KanbanColumn, KanbanCard } from './components';
import { Feature } from '@/store/app-store';
import { FastForward, Lightbulb, Archive } from 'lucide-react';
import { useKeyboardShortcutsConfig } from '@/hooks/use-keyboard-shortcuts';
import { useResponsiveKanban } from '@/hooks/use-responsive-kanban';
import { COLUMNS, ColumnId } from './constants';
interface KanbanBoardProps {
sensors: any;
@@ -90,20 +83,18 @@ export function KanbanBoard({
onArchiveAllVerified,
}: KanbanBoardProps) {
// Use responsive column widths based on window size
const { columnWidth } = useResponsiveKanban(COLUMNS.length);
// containerStyle handles centering and ensures columns fit without horizontal scroll in Electron
const { columnWidth, containerStyle } = useResponsiveKanban(COLUMNS.length);
return (
<div
className="flex-1 overflow-x-auto px-4 pb-4 relative"
style={backgroundImageStyle}
>
<div className="flex-1 overflow-x-hidden px-5 pb-4 relative" style={backgroundImageStyle}>
<DndContext
sensors={sensors}
collisionDetection={collisionDetectionStrategy}
onDragStart={onDragStart}
onDragEnd={onDragEnd}
>
<div className="flex gap-5 h-full py-1 justify-center">
<div className="h-full py-1" style={containerStyle}>
{COLUMNS.map((column) => {
const columnFeatures = getColumnFeatures(column.id);
return (
@@ -118,8 +109,7 @@ export function KanbanBoard({
showBorder={backgroundSettings.columnBorderEnabled}
hideScrollbar={backgroundSettings.hideScrollbar}
headerAction={
column.id === "verified" &&
columnFeatures.length > 0 ? (
column.id === 'verified' && columnFeatures.length > 0 ? (
<Button
variant="ghost"
size="sm"
@@ -130,7 +120,7 @@ export function KanbanBoard({
<Archive className="w-3 h-3 mr-1" />
Archive All
</Button>
) : column.id === "backlog" ? (
) : column.id === 'backlog' ? (
<div className="flex items-center gap-1">
<Button
variant="ghost"
@@ -175,9 +165,8 @@ export function KanbanBoard({
{columnFeatures.map((feature, index) => {
// Calculate shortcut key for in-progress cards (first 10 get 1-9, 0)
let shortcutKey: string | undefined;
if (column.id === "in_progress" && index < 10) {
shortcutKey =
index === 9 ? "0" : String(index + 1);
if (column.id === 'in_progress' && index < 10) {
shortcutKey = index === 9 ? '0' : String(index + 1);
}
return (
<KanbanCard
@@ -190,29 +179,19 @@ export function KanbanBoard({
onResume={() => onResume(feature)}
onForceStop={() => onForceStop(feature)}
onManualVerify={() => onManualVerify(feature)}
onMoveBackToInProgress={() =>
onMoveBackToInProgress(feature)
}
onMoveBackToInProgress={() => onMoveBackToInProgress(feature)}
onFollowUp={() => onFollowUp(feature)}
onComplete={() => onComplete(feature)}
onImplement={() => onImplement(feature)}
onViewPlan={() => onViewPlan(feature)}
onApprovePlan={() => onApprovePlan(feature)}
hasContext={featuresWithContext.has(feature.id)}
isCurrentAutoTask={runningAutoTasks.includes(
feature.id
)}
isCurrentAutoTask={runningAutoTasks.includes(feature.id)}
shortcutKey={shortcutKey}
opacity={backgroundSettings.cardOpacity}
glassmorphism={
backgroundSettings.cardGlassmorphism
}
cardBorderEnabled={
backgroundSettings.cardBorderEnabled
}
cardBorderOpacity={
backgroundSettings.cardBorderOpacity
}
glassmorphism={backgroundSettings.cardGlassmorphism}
cardBorderEnabled={backgroundSettings.cardBorderEnabled}
cardBorderOpacity={backgroundSettings.cardBorderOpacity}
/>
);
})}
@@ -225,7 +204,7 @@ export function KanbanBoard({
<DragOverlay
dropAnimation={{
duration: 200,
easing: "cubic-bezier(0.18, 0.67, 0.6, 1.22)",
easing: 'cubic-bezier(0.18, 0.67, 0.6, 1.22)',
}}
>
{activeFeature && (