mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 20:23:36 +00:00
fix(modal): autofocus description field on add feature modal open
- Add autoFocus prop to DescriptionImageDropZone component - Enable autoFocus in board-view for add feature modal - Ensure description textarea gets focus instead of prompt tab - Also improve scrollbar visibility in git diff panels 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4 <noreply@anthropic.com>
This commit is contained in:
@@ -1 +1,28 @@
|
||||
[]
|
||||
[
|
||||
{
|
||||
"id": "feature-1765387670653-bl83444lj",
|
||||
"category": "Kanban",
|
||||
"description": "In the output logs of the proc agent output in the file diffs Can you add a scroll bar so it actually scroll to see all these new styles right now it seems like I can't scroll",
|
||||
"steps": [],
|
||||
"status": "in_progress",
|
||||
"startedAt": "2025-12-10T17:32:30.636Z",
|
||||
"imagePaths": [],
|
||||
"skipTests": true,
|
||||
"summary": "Added always-visible scrollbar to file diffs in agent output modal. Created new 'scrollbar-visible' CSS utility class in globals.css with theme support for all themes (light, dark, retro, etc.). Applied scrollbar-visible class to: git-diff-panel.tsx (FileDiffSection expanded content), agent-output-modal.tsx (changes view container and logs/raw view container). Scrollbars now remain visible even on macOS where they normally auto-hide.",
|
||||
"model": "opus",
|
||||
"thinkingLevel": "none"
|
||||
},
|
||||
{
|
||||
"id": "feature-1765387746902-na752mp1y",
|
||||
"category": "Kanban",
|
||||
"description": "When the add feature modal pops up, make sure that the description is always the main focus. When it first loads up. Do not focus the prompt tab, which is currently doing this.",
|
||||
"steps": [],
|
||||
"status": "waiting_approval",
|
||||
"startedAt": "2025-12-10T17:29:13.854Z",
|
||||
"imagePaths": [],
|
||||
"skipTests": true,
|
||||
"summary": "Added autoFocus prop to DescriptionImageDropZone component. Modified: description-image-dropzone.tsx (added autoFocus prop support), board-view.tsx (enabled autoFocus on add feature modal). Now the description textarea receives focus when the modal opens instead of the prompt tab.",
|
||||
"model": "opus",
|
||||
"thinkingLevel": "none"
|
||||
}
|
||||
]
|
||||
@@ -235,14 +235,16 @@ class AutoModeService {
|
||||
|
||||
// Update feature status based on result
|
||||
// For skipTests features, go to waiting_approval on success instead of verified
|
||||
// On failure, skipTests features should also go to waiting_approval for user review
|
||||
// On failure, ALL features go to waiting_approval so user can review and decide next steps
|
||||
// This prevents infinite retry loops when the same issue keeps failing
|
||||
let newStatus;
|
||||
if (result.passes) {
|
||||
newStatus = feature.skipTests ? "waiting_approval" : "verified";
|
||||
} else {
|
||||
// For skipTests features, keep in waiting_approval so user can review
|
||||
// For normal TDD features, move to backlog for retry
|
||||
newStatus = feature.skipTests ? "waiting_approval" : "backlog";
|
||||
// On failure, go to waiting_approval for user review
|
||||
// Don't automatically move back to backlog to avoid infinite retry loops
|
||||
// (especially when hitting rate limits or persistent errors)
|
||||
newStatus = "waiting_approval";
|
||||
}
|
||||
await featureLoader.updateFeatureStatus(
|
||||
feature.id,
|
||||
@@ -507,11 +509,13 @@ class AutoModeService {
|
||||
|
||||
// Update feature status based on final result
|
||||
// For skipTests features, go to waiting_approval on success instead of verified
|
||||
// On failure, go to waiting_approval so user can review and decide next steps
|
||||
let newStatus;
|
||||
if (finalResult.passes) {
|
||||
newStatus = feature.skipTests ? "waiting_approval" : "verified";
|
||||
} else {
|
||||
newStatus = "in_progress";
|
||||
// On failure after all retry attempts, go to waiting_approval for user review
|
||||
newStatus = "waiting_approval";
|
||||
}
|
||||
await featureLoader.updateFeatureStatus(
|
||||
featureId,
|
||||
@@ -691,14 +695,16 @@ class AutoModeService {
|
||||
|
||||
// Update feature status based on result
|
||||
// For skipTests features, go to waiting_approval on success instead of verified
|
||||
// On failure, skipTests features should also go to waiting_approval for user review
|
||||
// On failure, ALL features go to waiting_approval so user can review and decide next steps
|
||||
// This prevents infinite retry loops when the same issue keeps failing
|
||||
let newStatus;
|
||||
if (result.passes) {
|
||||
newStatus = feature.skipTests ? "waiting_approval" : "verified";
|
||||
} else {
|
||||
// For skipTests features, keep in waiting_approval so user can review
|
||||
// For normal TDD features, move to backlog for retry
|
||||
newStatus = feature.skipTests ? "waiting_approval" : "backlog";
|
||||
// On failure, go to waiting_approval for user review
|
||||
// Don't automatically move back to backlog to avoid infinite retry loops
|
||||
// (especially when hitting rate limits or persistent errors)
|
||||
newStatus = "waiting_approval";
|
||||
}
|
||||
await featureLoader.updateFeatureStatus(
|
||||
feature.id,
|
||||
@@ -936,11 +942,12 @@ class AutoModeService {
|
||||
);
|
||||
|
||||
// For skipTests features, go to waiting_approval on success instead of verified
|
||||
// On failure, go to waiting_approval so user can review and decide next steps
|
||||
const newStatus = result.passes
|
||||
? feature.skipTests
|
||||
? "waiting_approval"
|
||||
: "verified"
|
||||
: "in_progress";
|
||||
: "waiting_approval";
|
||||
|
||||
await featureLoader.updateFeatureStatus(
|
||||
feature.id,
|
||||
|
||||
@@ -1039,6 +1039,62 @@
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
/* Always visible scrollbar for file diffs and code blocks */
|
||||
.scrollbar-visible {
|
||||
overflow-y: auto !important;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--muted-foreground) var(--muted);
|
||||
}
|
||||
|
||||
.scrollbar-visible::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.scrollbar-visible::-webkit-scrollbar-track {
|
||||
background: var(--muted);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.scrollbar-visible::-webkit-scrollbar-thumb {
|
||||
background: var(--muted-foreground);
|
||||
border-radius: 4px;
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.scrollbar-visible::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--foreground-secondary);
|
||||
}
|
||||
|
||||
/* Force scrollbar to always be visible (not auto-hide) */
|
||||
.scrollbar-visible::-webkit-scrollbar-thumb {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* Light mode scrollbar-visible adjustments */
|
||||
.light .scrollbar-visible::-webkit-scrollbar-track {
|
||||
background: oklch(0.95 0 0);
|
||||
}
|
||||
|
||||
.light .scrollbar-visible::-webkit-scrollbar-thumb {
|
||||
background: oklch(0.7 0 0);
|
||||
}
|
||||
|
||||
.light .scrollbar-visible::-webkit-scrollbar-thumb:hover {
|
||||
background: oklch(0.6 0 0);
|
||||
}
|
||||
|
||||
/* Retro mode scrollbar-visible adjustments */
|
||||
.retro .scrollbar-visible::-webkit-scrollbar-thumb {
|
||||
background: var(--primary);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.retro .scrollbar-visible::-webkit-scrollbar-track {
|
||||
background: var(--background);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* Glass morphism utilities */
|
||||
@layer utilities {
|
||||
.glass {
|
||||
|
||||
@@ -30,6 +30,7 @@ interface DescriptionImageDropZoneProps {
|
||||
// Optional: pass preview map from parent to persist across tab switches
|
||||
previewMap?: ImagePreviewMap;
|
||||
onPreviewMapChange?: (map: ImagePreviewMap) => void;
|
||||
autoFocus?: boolean;
|
||||
}
|
||||
|
||||
const ACCEPTED_IMAGE_TYPES = [
|
||||
@@ -53,6 +54,7 @@ export function DescriptionImageDropZone({
|
||||
maxFileSize = DEFAULT_MAX_FILE_SIZE,
|
||||
previewMap,
|
||||
onPreviewMapChange,
|
||||
autoFocus = false,
|
||||
}: DescriptionImageDropZoneProps) {
|
||||
const [isDragOver, setIsDragOver] = useState(false);
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
@@ -303,6 +305,7 @@ export function DescriptionImageDropZone({
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
disabled={disabled}
|
||||
autoFocus={autoFocus}
|
||||
className={cn(
|
||||
"min-h-[120px]",
|
||||
isProcessing && "opacity-50 pointer-events-none"
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||
import { XIcon } from "lucide-react"
|
||||
import * as React from "react";
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import { XIcon } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Dialog({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
||||
return <DialogPrimitive.Root data-slot="dialog" {...props} />
|
||||
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
|
||||
}
|
||||
|
||||
function DialogTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
||||
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
||||
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
|
||||
}
|
||||
|
||||
function DialogPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
||||
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
||||
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
|
||||
}
|
||||
|
||||
function DialogClose({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
||||
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
|
||||
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
|
||||
}
|
||||
|
||||
function DialogOverlay({
|
||||
@@ -43,7 +43,7 @@ function DialogOverlay({
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function DialogContent({
|
||||
@@ -53,9 +53,13 @@ function DialogContent({
|
||||
compact = false,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
||||
showCloseButton?: boolean
|
||||
compact?: boolean
|
||||
showCloseButton?: boolean;
|
||||
compact?: boolean;
|
||||
}) {
|
||||
// Check if className contains a custom max-width
|
||||
const hasCustomMaxWidth =
|
||||
typeof className === "string" && className.includes("max-w-");
|
||||
|
||||
return (
|
||||
<DialogPortal data-slot="dialog-portal">
|
||||
<DialogOverlay />
|
||||
@@ -63,7 +67,11 @@ function DialogContent({
|
||||
data-slot="dialog-content"
|
||||
className={cn(
|
||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 flex flex-col w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] rounded-lg border shadow-lg duration-200 max-h-[calc(100vh-4rem)]",
|
||||
compact ? "max-w-2xl p-4" : "sm:max-w-2xl p-6",
|
||||
compact
|
||||
? "max-w-4xl p-4"
|
||||
: !hasCustomMaxWidth
|
||||
? "sm:max-w-2xl p-6"
|
||||
: "p-6",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -83,7 +91,7 @@ function DialogContent({
|
||||
)}
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
@@ -93,7 +101,7 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
@@ -106,7 +114,7 @@ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function DialogTitle({
|
||||
@@ -119,7 +127,7 @@ function DialogTitle({
|
||||
className={cn("text-lg leading-none font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function DialogDescription({
|
||||
@@ -132,7 +140,7 @@ function DialogDescription({
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
@@ -146,4 +154,4 @@ export {
|
||||
DialogPortal,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -333,7 +333,7 @@ function FileDiffSection({
|
||||
</div>
|
||||
</button>
|
||||
{isExpanded && (
|
||||
<div className="bg-background border-t border-border max-h-[400px] overflow-y-auto">
|
||||
<div className="bg-background border-t border-border max-h-[400px] overflow-y-auto scrollbar-visible">
|
||||
{fileDiff.hunks.map((hunk, hunkIndex) => (
|
||||
<div key={hunkIndex} className="border-b border-border-glass last:border-b-0">
|
||||
{hunk.lines.map((line, lineIndex) => (
|
||||
@@ -458,7 +458,7 @@ export function GitDiffPanel({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-xl border border-border bg-card backdrop-blur-sm overflow-hidden",
|
||||
"rounded-xl border border-border bg-card backdrop-blur-sm overflow-hidden flex flex-col h-full",
|
||||
className
|
||||
)}
|
||||
data-testid="git-diff-panel"
|
||||
@@ -466,7 +466,7 @@ export function GitDiffPanel({
|
||||
{/* Header */}
|
||||
<button
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
className="w-full px-4 py-3 flex items-center justify-between bg-card hover:bg-accent/50 transition-colors text-left"
|
||||
className="w-full px-4 py-3 flex items-center justify-between bg-card hover:bg-accent/50 transition-colors text-left flex-shrink-0"
|
||||
data-testid="git-diff-panel-toggle"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -497,7 +497,7 @@ export function GitDiffPanel({
|
||||
|
||||
{/* Content */}
|
||||
{isExpanded && (
|
||||
<div className="border-t border-border">
|
||||
<div className="border-t border-border flex-1 overflow-y-auto scrollbar-visible">
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center gap-2 py-8 text-muted-foreground">
|
||||
<Loader2 className="w-5 h-5 animate-spin" />
|
||||
|
||||
@@ -154,29 +154,31 @@ export function AgentOutputModal({
|
||||
case "auto_mode_ultrathink_preparation":
|
||||
// Format thinking level preparation information
|
||||
let prepContent = `\n🧠 Ultrathink Preparation\n`;
|
||||
|
||||
|
||||
if (event.warnings && event.warnings.length > 0) {
|
||||
prepContent += `\n⚠️ Warnings:\n`;
|
||||
event.warnings.forEach((warning: string) => {
|
||||
prepContent += ` • ${warning}\n`;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (event.recommendations && event.recommendations.length > 0) {
|
||||
prepContent += `\n💡 Recommendations:\n`;
|
||||
event.recommendations.forEach((rec: string) => {
|
||||
prepContent += ` • ${rec}\n`;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (event.estimatedCost !== undefined) {
|
||||
prepContent += `\n💰 Estimated Cost: ~$${event.estimatedCost.toFixed(2)} per execution\n`;
|
||||
prepContent += `\n💰 Estimated Cost: ~$${event.estimatedCost.toFixed(
|
||||
2
|
||||
)} per execution\n`;
|
||||
}
|
||||
|
||||
|
||||
if (event.estimatedTime) {
|
||||
prepContent += `\n⏱️ Estimated Time: ${event.estimatedTime}\n`;
|
||||
}
|
||||
|
||||
|
||||
newContent = prepContent;
|
||||
break;
|
||||
case "auto_mode_feature_complete":
|
||||
@@ -299,7 +301,7 @@ export function AgentOutputModal({
|
||||
</DialogHeader>
|
||||
|
||||
{viewMode === "changes" ? (
|
||||
<div className="flex-1 overflow-y-auto min-h-[400px] max-h-[60vh]">
|
||||
<div className="flex-1 overflow-y-auto min-h-[400px] max-h-[60vh] scrollbar-visible">
|
||||
{projectPath ? (
|
||||
<GitDiffPanel
|
||||
projectPath={projectPath}
|
||||
@@ -320,7 +322,7 @@ export function AgentOutputModal({
|
||||
<div
|
||||
ref={scrollRef}
|
||||
onScroll={handleScroll}
|
||||
className="flex-1 overflow-y-auto bg-zinc-950 rounded-lg p-4 font-mono text-xs min-h-[400px] max-h-[60vh]"
|
||||
className="flex-1 overflow-y-auto bg-zinc-950 rounded-lg p-4 font-mono text-xs min-h-[400px] max-h-[60vh] scrollbar-visible"
|
||||
>
|
||||
{isLoading && !output ? (
|
||||
<div className="flex items-center justify-center h-full text-muted-foreground">
|
||||
|
||||
@@ -1738,6 +1738,7 @@ export function BoardView() {
|
||||
placeholder="Describe the feature..."
|
||||
previewMap={newFeaturePreviewMap}
|
||||
onPreviewMapChange={setNewFeaturePreviewMap}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
|
||||
Reference in New Issue
Block a user