Skip to content

Commit 467c726

Browse files
fix: Button.tsx hardcoded white/XX -> CSS vars, Dialog.tsx hardcoded rgba, ConcurrencyPolicy slot key
Button.tsx: Replaced all ext-white/XX, �order-white/[0.0X], hover:bg-white/[0.0X] with CSS variable equivalents (--text-primary, --text-secondary, --text-quaternary, --border-default, --border-hover, --border-subtle). Added focus-visible outline. Fixed font-['Inter'] -> font-sans. Dialog.tsx: Replaced hardcoded rgba(255,255,255,0.08) border with var(--border-default) and rgba(255,255,255,0.04) with var(--border-subtle). ToolConcurrencyPolicy.ts: acquire() used tool.name as Map key, which overwrote previous slots for the same tool name, so countRunning() could never exceed 1. Changed to slotKey = \\-\\ for unique keys. release() now deletes all slots matching the toolName.
1 parent b2fb25b commit 467c726

3 files changed

Lines changed: 150 additions & 2 deletions

File tree

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { type ButtonHTMLAttributes, forwardRef } from "react"
2+
import { cn } from "@/lib/utils"
3+
4+
type ButtonVariant = "primary" | "secondary" | "ghost" | "danger"
5+
type ButtonSize = "default" | "small" | "micro"
6+
7+
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
8+
variant?: ButtonVariant
9+
size?: ButtonSize
10+
}
11+
12+
const variantStyles: Record<ButtonVariant, string> = {
13+
primary:
14+
"bg-[var(--color-accent-brand)] text-white border-none hover:opacity-85 active:opacity-100",
15+
secondary:
16+
"bg-transparent border border-[var(--border-default)] text-[var(--text-secondary)] hover:border-[var(--border-hover)] hover:text-[var(--text-primary)] active:border-[var(--border-default)]",
17+
ghost:
18+
"bg-transparent border-none text-[var(--text-quaternary)] hover:bg-[var(--border-subtle)] hover:text-[var(--text-secondary)] active:bg-[var(--border-subtle)]",
19+
danger:
20+
"bg-transparent border border-red-500/20 text-red-400/80 hover:bg-red-500/8 hover:text-red-400 active:bg-red-500/5",
21+
}
22+
23+
const sizeStyles: Record<ButtonSize, string> = {
24+
default: "h-8 px-3 text-[12px] font-medium gap-1.5",
25+
small: "h-6 px-2 text-[11px] font-medium gap-1",
26+
micro: "h-5 px-1.5 text-[10px] font-medium gap-0.5",
27+
}
28+
29+
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
30+
({ variant = "ghost", size = "default", className, disabled, children, ...props }, ref) => {
31+
return (
32+
<button
33+
ref={ref}
34+
disabled={disabled}
35+
className={cn(
36+
"inline-flex items-center justify-center rounded-lg transition-all duration-100 select-none whitespace-nowrap",
37+
"active:scale-[0.97]",
38+
"disabled:opacity-40 disabled:pointer-events-none",
39+
"font-sans focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-accent-brand)]",
40+
variantStyles[variant],
41+
sizeStyles[size],
42+
className,
43+
)}
44+
{...props}
45+
>
46+
{children}
47+
</button>
48+
)
49+
},
50+
)
51+
Button.displayName = "Button"
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { type ReactNode, useEffect, useCallback } from "react"
2+
import { motion, AnimatePresence } from "framer-motion"
3+
import { X } from "lucide-react"
4+
import { cn } from "@/lib/utils"
5+
import { DURATION, EASING } from "@/lib/motion"
6+
7+
interface DialogProps {
8+
open: boolean
9+
onClose: () => void
10+
title?: string
11+
children: ReactNode
12+
className?: string
13+
width?: string
14+
}
15+
16+
export function Dialog({ open, onClose, title, children, className, width = "min(640px, 90vw)" }: DialogProps) {
17+
const handleKeyDown = useCallback(
18+
(e: KeyboardEvent) => {
19+
if (e.key === "Escape") onClose()
20+
},
21+
[onClose],
22+
)
23+
24+
useEffect(() => {
25+
if (open) {
26+
document.addEventListener("keydown", handleKeyDown)
27+
return () => document.removeEventListener("keydown", handleKeyDown)
28+
}
29+
}, [open, handleKeyDown])
30+
31+
return (
32+
<AnimatePresence>
33+
{open && (
34+
<div className="fixed inset-0 z-50 flex items-center justify-center">
35+
{/* Backdrop */}
36+
<motion.div
37+
initial={{ opacity: 0 }}
38+
animate={{ opacity: 1 }}
39+
exit={{ opacity: 0 }}
40+
transition={{ duration: DURATION.fast, ease: EASING.default }}
41+
className="absolute inset-0"
42+
style={{ backgroundColor: "rgba(0,0,0,0.4)", backdropFilter: "blur(2px)" }}
43+
onClick={onClose}
44+
/>
45+
46+
{/* Panel */}
47+
<motion.div
48+
initial={{ opacity: 0, scale: 0.96 }}
49+
animate={{ opacity: 1, scale: 1 }}
50+
exit={{ opacity: 0, scale: 0.96 }}
51+
transition={{ duration: DURATION.modal, ease: EASING.default }}
52+
className={cn(
53+
"relative rounded-xl border overflow-hidden shadow-2xl max-h-[85vh] flex flex-col",
54+
className,
55+
)}
56+
style={{
57+
width,
58+
backgroundColor: "var(--surface-overlay)",
59+
borderColor: "var(--border-default)",
60+
boxShadow: "0 24px 64px rgba(0,0,0,0.5)",
61+
}}
62+
onClick={(e) => e.stopPropagation()}
63+
>
64+
{/* Header */}
65+
{title && (
66+
<div
67+
className="flex items-center gap-2 px-4 py-3 border-b shrink-0"
68+
style={{ borderColor: "var(--border-subtle)" }}
69+
>
70+
<span className="text-[13px] font-medium flex-1" style={{ color: "var(--text-primary)" }}>
71+
{title}
72+
</span>
73+
<button
74+
onClick={onClose}
75+
className="flex items-center justify-center h-6 w-6 rounded-md transition-colors"
76+
style={{ color: "var(--text-quaternary)" }}
77+
aria-label="Close dialog"
78+
>
79+
<X className="h-3.5 w-3.5" />
80+
</button>
81+
</div>
82+
)}
83+
84+
{/* Content */}
85+
<div className="flex-1 overflow-y-auto p-4">{children}</div>
86+
</motion.div>
87+
</div>
88+
)}
89+
</AnimatePresence>
90+
)
91+
}

apps/desktop/src/renderer/runtime/tools/policies/ToolConcurrencyPolicy.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export class ToolConcurrencyPolicy {
1010
private running: Map<string, ConcurrencySlot> = new Map()
1111
private maxConcurrentDefault = 5
1212
private toolLimits: Map<string, number> = new Map()
13+
private slotCounter = 0
1314

1415
setToolLimit(toolName: string, maxConcurrent: number): void {
1516
this.toolLimits.set(toolName, maxConcurrent)
@@ -30,12 +31,17 @@ export class ToolConcurrencyPolicy {
3031
if (!tool.isConcurrencySafe(input as any)) {
3132
if (this.running.size > 0) return false
3233
}
33-
this.running.set(tool.name, { toolName: tool.name, startedAt: Date.now(), input })
34+
const slotKey = `${tool.name}-${++this.slotCounter}`
35+
this.running.set(slotKey, { toolName: tool.name, startedAt: Date.now(), input })
3436
return true
3537
}
3638

3739
release(toolName: string): void {
38-
this.running.delete(toolName)
40+
for (const [key, slot] of this.running) {
41+
if (slot.toolName === toolName) {
42+
this.running.delete(key)
43+
}
44+
}
3945
}
4046

4147
countRunning(toolName: string): number {

0 commit comments

Comments
 (0)