ideation improvements and linting

This commit is contained in:
AndyMik90
2025-12-16 23:04:45 +01:00
parent afeb54fc2c
commit 36a69fcf0d
44 changed files with 793 additions and 254 deletions
+60 -60
View File
@@ -53,11 +53,11 @@ export default function App() {
<div className="min-h-screen p-8 transition-colors duration-300">
{/* Header */}
<div className="max-w-7xl mx-auto mb-8">
<Card className="!rounded-[var(--radius-2xl)]">
<Card className="rounded-2xl!">
<div className="flex items-center justify-between">
<div>
<h1 className="text-display-medium">Auto-Build Design System</h1>
<p className="text-body-large text-[var(--color-text-secondary)] mt-1">
<p className="text-body-large text-(--color-text-secondary) mt-1">
A modern, friendly design system for building beautiful interfaces
</p>
</div>
@@ -118,8 +118,8 @@ export default function App() {
<Card>
<div className="flex items-center justify-between mb-6">
<h2 className="text-heading-large">Color Palette</h2>
<p className="text-body-small text-[var(--color-text-tertiary)]">
Currently showing: <strong className="text-[var(--color-text-primary)]">{currentThemeInfo.name}</strong> theme
<p className="text-body-small text-(--color-text-tertiary)">
Currently showing: <strong className="text-(--color-text-primary)">{currentThemeInfo.name}</strong> theme
</p>
</div>
@@ -128,14 +128,14 @@ export default function App() {
<h3 className="text-heading-small mb-3">Background</h3>
<div className="flex gap-4">
<div className="text-center">
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-background-primary)] border border-[var(--color-border-default)]" />
<div className="w-20 h-20 rounded-lg bg-(--color-background-primary) border border-(--color-border-default)" />
<p className="text-label-small mt-2">Primary</p>
<p className="text-body-small text-[var(--color-text-tertiary)]">--bg-primary</p>
<p className="text-body-small text-(--color-text-tertiary)">--bg-primary</p>
</div>
<div className="text-center">
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-background-secondary)] border border-[var(--color-border-default)]" />
<div className="w-20 h-20 rounded-lg bg-(--color-background-secondary) border border-(--color-border-default)" />
<p className="text-label-small mt-2">Secondary</p>
<p className="text-body-small text-[var(--color-text-tertiary)]">--bg-secondary</p>
<p className="text-body-small text-(--color-text-tertiary)">--bg-secondary</p>
</div>
</div>
</div>
@@ -144,19 +144,19 @@ export default function App() {
<h3 className="text-heading-small mb-3">Accent</h3>
<div className="flex gap-4">
<div className="text-center">
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-accent-primary)]" />
<div className="w-20 h-20 rounded-lg bg-(--color-accent-primary)" />
<p className="text-label-small mt-2">Primary</p>
<p className="text-body-small text-[var(--color-text-tertiary)]">--accent</p>
<p className="text-body-small text-(--color-text-tertiary)">--accent</p>
</div>
<div className="text-center">
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-accent-primary-hover)]" />
<div className="w-20 h-20 rounded-lg bg-(--color-accent-primary-hover)" />
<p className="text-label-small mt-2">Hover</p>
<p className="text-body-small text-[var(--color-text-tertiary)]">--accent-hover</p>
<p className="text-body-small text-(--color-text-tertiary)">--accent-hover</p>
</div>
<div className="text-center">
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-accent-primary-light)] border border-[var(--color-border-default)]" />
<div className="w-20 h-20 rounded-lg bg-(--color-accent-primary-light) border border-(--color-border-default)" />
<p className="text-label-small mt-2">Light</p>
<p className="text-body-small text-[var(--color-text-tertiary)]">--accent-light</p>
<p className="text-body-small text-(--color-text-tertiary)">--accent-light</p>
</div>
</div>
</div>
@@ -165,24 +165,24 @@ export default function App() {
<h3 className="text-heading-small mb-3">Semantic</h3>
<div className="flex gap-4">
<div className="text-center">
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-semantic-success)]" />
<div className="w-20 h-20 rounded-lg bg-(--color-semantic-success)" />
<p className="text-label-small mt-2">Success</p>
<p className="text-body-small text-[var(--color-text-tertiary)]">--success</p>
<p className="text-body-small text-(--color-text-tertiary)">--success</p>
</div>
<div className="text-center">
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-semantic-warning)]" />
<div className="w-20 h-20 rounded-lg bg-(--color-semantic-warning)" />
<p className="text-label-small mt-2">Warning</p>
<p className="text-body-small text-[var(--color-text-tertiary)]">--warning</p>
<p className="text-body-small text-(--color-text-tertiary)">--warning</p>
</div>
<div className="text-center">
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-semantic-error)]" />
<div className="w-20 h-20 rounded-lg bg-(--color-semantic-error)" />
<p className="text-label-small mt-2">Error</p>
<p className="text-body-small text-[var(--color-text-tertiary)]">--error</p>
<p className="text-body-small text-(--color-text-tertiary)">--error</p>
</div>
<div className="text-center">
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-semantic-info)]" />
<div className="w-20 h-20 rounded-lg bg-(--color-semantic-info)" />
<p className="text-label-small mt-2">Info</p>
<p className="text-body-small text-[var(--color-text-tertiary)]">--info</p>
<p className="text-body-small text-(--color-text-tertiary)">--info</p>
</div>
</div>
</div>
@@ -191,29 +191,29 @@ export default function App() {
<h3 className="text-heading-small mb-3">Text</h3>
<div className="flex gap-4">
<div className="text-center">
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-text-primary)]" />
<div className="w-20 h-20 rounded-lg bg-(--color-text-primary)" />
<p className="text-label-small mt-2">Primary</p>
<p className="text-body-small text-[var(--color-text-tertiary)]">--text-primary</p>
<p className="text-body-small text-(--color-text-tertiary)">--text-primary</p>
</div>
<div className="text-center">
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-text-secondary)]" />
<div className="w-20 h-20 rounded-lg bg-(--color-text-secondary)" />
<p className="text-label-small mt-2">Secondary</p>
<p className="text-body-small text-[var(--color-text-tertiary)]">--text-secondary</p>
<p className="text-body-small text-(--color-text-tertiary)">--text-secondary</p>
</div>
<div className="text-center">
<div className="w-20 h-20 rounded-[var(--radius-lg)] bg-[var(--color-text-tertiary)]" />
<div className="w-20 h-20 rounded-lg bg-(--color-text-tertiary)" />
<p className="text-label-small mt-2">Tertiary</p>
<p className="text-body-small text-[var(--color-text-tertiary)]">--text-tertiary</p>
<p className="text-body-small text-(--color-text-tertiary)">--text-tertiary</p>
</div>
</div>
</div>
</div>
{/* Theme-specific color values */}
<div className="mt-8 p-4 bg-[var(--color-background-secondary)] rounded-[var(--radius-lg)]">
<p className="text-body-small text-[var(--color-text-secondary)]">
<div className="mt-8 p-4 bg-(--color-background-secondary) rounded-lg">
<p className="text-body-small text-(--color-text-secondary)">
<strong>Note:</strong> Colors vary by theme and mode. Switch themes using the dropdown above to see different palettes.
For specific hex values, see the <strong>Themes</strong> tab or check <code className="font-mono bg-[var(--color-background-neutral)] px-1 rounded">design.json</code>.
For specific hex values, see the <strong>Themes</strong> tab or check <code className="font-mono bg-(--color-background-neutral) px-1 rounded">design.json</code>.
</p>
</div>
</Card>
@@ -226,36 +226,36 @@ export default function App() {
<h2 className="text-heading-large mb-6">Typography Scale</h2>
<div className="space-y-6">
<div className="border-b border-[var(--color-border-default)] pb-4">
<p className="text-label-small text-[var(--color-text-tertiary)] mb-2">Display Large 36px / 700</p>
<div className="border-b border-(--color-border-default) pb-4">
<p className="text-label-small text-(--color-text-tertiary) mb-2">Display Large 36px / 700</p>
<p className="text-display-large">The quick brown fox jumps</p>
</div>
<div className="border-b border-[var(--color-border-default)] pb-4">
<p className="text-label-small text-[var(--color-text-tertiary)] mb-2">Display Medium 30px / 700</p>
<div className="border-b border-(--color-border-default) pb-4">
<p className="text-label-small text-(--color-text-tertiary) mb-2">Display Medium 30px / 700</p>
<p className="text-display-medium">The quick brown fox jumps over</p>
</div>
<div className="border-b border-[var(--color-border-default)] pb-4">
<p className="text-label-small text-[var(--color-text-tertiary)] mb-2">Heading Large 24px / 600</p>
<div className="border-b border-(--color-border-default) pb-4">
<p className="text-label-small text-(--color-text-tertiary) mb-2">Heading Large 24px / 600</p>
<p className="text-heading-large">The quick brown fox jumps over the lazy dog</p>
</div>
<div className="border-b border-[var(--color-border-default)] pb-4">
<p className="text-label-small text-[var(--color-text-tertiary)] mb-2">Heading Medium 20px / 600</p>
<div className="border-b border-(--color-border-default) pb-4">
<p className="text-label-small text-(--color-text-tertiary) mb-2">Heading Medium 20px / 600</p>
<p className="text-heading-medium">The quick brown fox jumps over the lazy dog</p>
</div>
<div className="border-b border-[var(--color-border-default)] pb-4">
<p className="text-label-small text-[var(--color-text-tertiary)] mb-2">Heading Small 16px / 600</p>
<div className="border-b border-(--color-border-default) pb-4">
<p className="text-label-small text-(--color-text-tertiary) mb-2">Heading Small 16px / 600</p>
<p className="text-heading-small">The quick brown fox jumps over the lazy dog</p>
</div>
<div className="border-b border-[var(--color-border-default)] pb-4">
<p className="text-label-small text-[var(--color-text-tertiary)] mb-2">Body Large 16px / 400</p>
<div className="border-b border-(--color-border-default) pb-4">
<p className="text-label-small text-(--color-text-tertiary) mb-2">Body Large 16px / 400</p>
<p className="text-body-large">The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs.</p>
</div>
<div className="border-b border-[var(--color-border-default)] pb-4">
<p className="text-label-small text-[var(--color-text-tertiary)] mb-2">Body Medium 14px / 400</p>
<div className="border-b border-(--color-border-default) pb-4">
<p className="text-label-small text-(--color-text-tertiary) mb-2">Body Medium 14px / 400</p>
<p className="text-body-medium">The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs.</p>
</div>
<div>
<p className="text-label-small text-[var(--color-text-tertiary)] mb-2">Body Small 12px / 400</p>
<p className="text-label-small text-(--color-text-tertiary) mb-2">Body Small 12px / 400</p>
<p className="text-body-small">The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs.</p>
</div>
</div>
@@ -392,7 +392,7 @@ export default function App() {
<div className="space-y-8">
<Card>
<h2 className="text-heading-large mb-4">Animations</h2>
<p className="text-body-medium text-[var(--color-text-secondary)]">
<p className="text-body-medium text-(--color-text-secondary)">
Animation demos are available in the original file. Extract them to a separate AnimationsSection component for better organization.
</p>
</Card>
@@ -404,26 +404,26 @@ export default function App() {
<Card>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="p-2 rounded-[var(--radius-lg)] bg-[var(--color-accent-primary-light)]">
<Sparkles className="w-6 h-6 text-[var(--color-accent-primary)]" />
<div className="p-2 rounded-lg bg-(--color-accent-primary-light)">
<Sparkles className="w-6 h-6 text-(--color-accent-primary)" />
</div>
<div>
<h2 className="text-heading-large">Theme Gallery</h2>
<p className="text-body-medium text-[var(--color-text-secondary)]">
<p className="text-body-medium text-(--color-text-secondary)">
{themes.length} color themes × 2 modes = {themes.length * 2} combinations
</p>
</div>
</div>
{/* Mode Toggle */}
<div className="flex items-center gap-3 p-1 bg-[var(--color-background-secondary)] rounded-full">
<div className="flex items-center gap-3 p-1 bg-(--color-background-secondary) rounded-full">
<button
onClick={() => mode === 'dark' && toggleMode()}
className={cn(
"px-4 py-2 rounded-full text-body-medium font-medium transition-all",
mode === 'light'
? "bg-[var(--color-surface-card)] shadow-sm"
: "text-[var(--color-text-secondary)]"
? "bg-(--color-surface-card) shadow-sm"
: "text-(--color-text-secondary)"
)}
>
<Sun className="w-4 h-4 inline mr-2" />
@@ -434,8 +434,8 @@ export default function App() {
className={cn(
"px-4 py-2 rounded-full text-body-medium font-medium transition-all",
mode === 'dark'
? "bg-[var(--color-surface-card)] shadow-sm"
: "text-[var(--color-text-secondary)]"
? "bg-(--color-surface-card) shadow-sm"
: "text-(--color-text-secondary)"
)}
>
<Moon className="w-4 h-4 inline mr-2" />
@@ -454,10 +454,10 @@ export default function App() {
key={theme.id}
onClick={() => setColorTheme(theme.id)}
className={cn(
"p-6 rounded-[var(--radius-2xl)] text-left transition-all border-2",
"p-6 rounded-2xl text-left transition-all border-2",
colorTheme === theme.id
? "border-[var(--color-accent-primary)] bg-[var(--color-accent-primary-light)]"
: "border-[var(--color-border-default)] bg-[var(--color-surface-card)] hover:border-[var(--color-accent-primary)]/50"
? "border-(--color-accent-primary) bg-(--color-accent-primary-light)"
: "border-(--color-border-default) bg-(--color-surface-card) hover:border-(--color-accent-primary)/50"
)}
>
<div className="flex items-center gap-2 mb-3">
@@ -471,9 +471,9 @@ export default function App() {
/>
</div>
<h3 className="text-heading-small mb-1">{theme.name}</h3>
<p className="text-body-small text-[var(--color-text-tertiary)]">{theme.description}</p>
<p className="text-body-small text-(--color-text-tertiary)">{theme.description}</p>
{colorTheme === theme.id && (
<div className="mt-3 inline-flex items-center px-2 py-1 rounded-full bg-[var(--color-accent-primary)] text-white text-label-small">
<div className="mt-3 inline-flex items-center px-2 py-1 rounded-full bg-(--color-accent-primary) text-white text-label-small">
Active
</div>
)}
+4 -4
View File
@@ -28,8 +28,8 @@ export function Avatar({ src, name = 'User', size = 'md', color }: AvatarProps)
return (
<div
className={cn(
'rounded-full flex items-center justify-center font-semibold border-2 border-[var(--color-surface-card)] overflow-hidden',
!color && 'bg-[var(--color-border-default)]',
'rounded-full flex items-center justify-center font-semibold border-2 border-(--color-surface-card) overflow-hidden',
!color && 'bg-(--color-border-default)',
sizes[size]
)}
style={bgStyle}
@@ -37,7 +37,7 @@ export function Avatar({ src, name = 'User', size = 'md', color }: AvatarProps)
{src ? (
<img src={src} alt={name} className="w-full h-full object-cover" />
) : (
<span className={cn(color ? 'text-white' : 'text-[var(--color-text-primary)]')}>{initials}</span>
<span className={cn(color ? 'text-white' : 'text-(--color-text-primary)')}>{initials}</span>
)}
</div>
)
@@ -58,7 +58,7 @@ export function AvatarGroup({ avatars, max = 4 }: AvatarGroupProps) {
<Avatar key={i} {...avatar} size="sm" />
))}
{remaining > 0 && (
<div className="w-8 h-8 rounded-full bg-[var(--color-background-secondary)] flex items-center justify-center text-xs font-medium text-[var(--color-text-secondary)] border-2 border-[var(--color-surface-card)]">
<div className="w-8 h-8 rounded-full bg-(--color-background-secondary) flex items-center justify-center text-xs font-medium text-(--color-text-secondary) border-2 border-(--color-surface-card)">
+{remaining}
</div>
)}
+6 -6
View File
@@ -8,12 +8,12 @@ export interface BadgeProps {
export function Badge({ children, variant = 'default' }: BadgeProps) {
const variants = {
default: 'bg-[var(--color-background-secondary)] text-[var(--color-text-secondary)]',
primary: 'bg-[var(--color-accent-primary-light)] text-[var(--color-accent-primary)]',
success: 'bg-[var(--color-semantic-success-light)] text-[var(--color-semantic-success)]',
warning: 'bg-[var(--color-semantic-warning-light)] text-[var(--color-semantic-warning)]',
error: 'bg-[var(--color-semantic-error-light)] text-[var(--color-semantic-error)]',
outline: 'bg-transparent border border-[var(--color-border-default)] text-[var(--color-text-secondary)]'
default: 'bg-(--color-background-secondary) text-(--color-text-secondary)',
primary: 'bg-(--color-accent-primary-light) text-(--color-accent-primary)',
success: 'bg-(--color-semantic-success-light) text-(--color-semantic-success)',
warning: 'bg-(--color-semantic-warning-light) text-(--color-semantic-warning)',
error: 'bg-(--color-semantic-error-light) text-(--color-semantic-error)',
outline: 'bg-transparent border border-(--color-border-default) text-(--color-text-secondary)'
}
return (
+6 -6
View File
@@ -18,11 +18,11 @@ export function Button({
const baseStyles = 'inline-flex items-center justify-center font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2'
const variants = {
primary: 'bg-[var(--color-accent-primary)] text-[var(--color-text-inverse)] hover:bg-[var(--color-accent-primary-hover)] focus:ring-[var(--color-accent-primary)]',
secondary: 'bg-transparent border border-[var(--color-border-default)] text-[var(--color-text-primary)] hover:bg-[var(--color-background-secondary)]',
ghost: 'bg-transparent text-[var(--color-text-secondary)] hover:bg-[var(--color-background-secondary)]',
success: 'bg-[var(--color-semantic-success)] text-white hover:opacity-90',
danger: 'bg-[var(--color-semantic-error)] text-white hover:opacity-90'
primary: 'bg-(--color-accent-primary) text-(--color-text-inverse) hover:bg-(--color-accent-primary-hover) focus:ring-(--color-accent-primary)',
secondary: 'bg-transparent border border-(--color-border-default) text-(--color-text-primary) hover:bg-(--color-background-secondary)',
ghost: 'bg-transparent text-(--color-text-secondary) hover:bg-(--color-background-secondary)',
success: 'bg-(--color-semantic-success) text-white hover:opacity-90',
danger: 'bg-(--color-semantic-error) text-white hover:opacity-90'
}
const sizes = {
@@ -31,7 +31,7 @@ export function Button({
lg: 'h-12 px-6 text-base'
}
const radius = pill ? 'rounded-full' : 'rounded-[var(--radius-md)]'
const radius = pill ? 'rounded-full' : 'rounded-md'
return (
<button
+1 -1
View File
@@ -14,7 +14,7 @@ export function Card({
}: CardProps) {
return (
<div className={cn(
'bg-[var(--color-surface-card)] rounded-[var(--radius-xl)] shadow-[var(--shadow-md)]',
'bg-(--color-surface-card) rounded-xl shadow-md',
padding && 'p-6',
className
)}>
+5 -5
View File
@@ -9,12 +9,12 @@ export function Input({
return (
<input
className={cn(
'h-10 w-full px-4 rounded-[var(--radius-md)] border border-[var(--color-border-default)]',
'bg-[var(--color-surface-card)] text-[var(--color-text-primary)] text-sm',
'focus:outline-none focus:border-[var(--color-accent-primary)] focus:ring-2 focus:ring-[var(--color-accent-primary)]/20',
'placeholder:text-[var(--color-text-tertiary)]',
'h-10 w-full px-4 rounded-md border border-(--color-border-default)',
'bg-(--color-surface-card) text-(--color-text-primary) text-sm',
'focus:outline-none focus:border-(--color-accent-primary) focus:ring-2 focus:ring-(--color-accent-primary)/20',
'placeholder:text-(--color-text-tertiary)',
'transition-all duration-200',
'disabled:bg-[var(--color-background-secondary)] disabled:opacity-60',
'disabled:bg-(--color-background-secondary) disabled:opacity-60',
className
)}
placeholder={placeholder}
+1 -1
View File
@@ -14,7 +14,7 @@ export function Toggle({ checked, onChange }: ToggleProps) {
onClick={() => onChange(!checked)}
className={cn(
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors duration-200',
checked ? 'bg-[var(--color-accent-primary)]' : 'bg-[var(--color-border-default)]'
checked ? 'bg-(--color-accent-primary)' : 'bg-(--color-border-default)'
)}
>
<span
+10 -10
View File
@@ -15,18 +15,18 @@ export function CalendarCard() {
return (
<Card className="w-[300px]">
<div className="flex items-center justify-between mb-4">
<button className="p-1 hover:bg-[var(--color-background-secondary)] rounded transition-colors">
<ChevronLeft className="w-5 h-5 text-[var(--color-text-tertiary)]" />
<button className="p-1 hover:bg-(--color-background-secondary) rounded transition-colors">
<ChevronLeft className="w-5 h-5 text-(--color-text-tertiary)" />
</button>
<h3 className="text-heading-small">February, 2021</h3>
<button className="p-1 hover:bg-[var(--color-background-secondary)] rounded transition-colors">
<ChevronRight className="w-5 h-5 text-[var(--color-text-tertiary)]" />
<button className="p-1 hover:bg-(--color-background-secondary) rounded transition-colors">
<ChevronRight className="w-5 h-5 text-(--color-text-tertiary)" />
</button>
</div>
<div className="grid grid-cols-7 gap-1 text-center">
{days.map((day, i) => (
<div key={i} className="text-label-small text-[var(--color-text-tertiary)] py-2">
<div key={i} className="text-label-small text-(--color-text-tertiary) py-2">
{day}
</div>
))}
@@ -39,11 +39,11 @@ export function CalendarCard() {
<button
key={i}
className={cn(
'w-9 h-9 rounded-[var(--radius-md)] text-body-medium transition-colors',
!isCurrentMonth && 'text-[var(--color-text-tertiary)]',
isSelected && 'bg-[var(--color-accent-primary)] text-[var(--color-text-inverse)] rounded-full',
isToday && !isSelected && 'text-[var(--color-accent-primary)] font-semibold',
!isSelected && 'hover:bg-[var(--color-background-secondary)]'
'w-9 h-9 rounded-md text-body-medium transition-colors',
!isCurrentMonth && 'text-(--color-text-tertiary)',
isSelected && 'bg-(--color-accent-primary) text-(--color-text-inverse) rounded-full',
isToday && !isSelected && 'text-(--color-accent-primary) font-semibold',
!isSelected && 'hover:bg-(--color-background-secondary)'
)}
>
{date}
@@ -21,14 +21,14 @@ export function IntegrationsCard() {
{integrations.map((int, i) => (
<div key={i} className="flex items-center gap-3">
<div
className="w-10 h-10 rounded-[var(--radius-lg)] flex items-center justify-center"
className="w-10 h-10 rounded-lg flex items-center justify-center"
style={{ backgroundColor: `${int.color}15` }}
>
<int.icon className="w-5 h-5" style={{ color: int.color }} />
</div>
<div className="flex-1 min-w-0">
<p className="text-heading-small">{int.name}</p>
<p className="text-body-small text-[var(--color-text-secondary)] truncate">{int.desc}</p>
<p className="text-body-small text-(--color-text-secondary) truncate">{int.desc}</p>
</div>
<Toggle checked={int.enabled} onChange={int.toggle} />
</div>
@@ -10,14 +10,14 @@ export function MilestoneCard() {
<div className="flex items-center gap-6">
<div>
<p className="text-body-small text-[var(--color-text-secondary)]">Due date:</p>
<p className="text-body-small text-(--color-text-secondary)">Due date:</p>
<p className="text-heading-small">March 20th</p>
</div>
<ProgressCircle value={39} size="lg" />
<div>
<p className="text-body-small text-[var(--color-text-secondary)]">Asignees:</p>
<p className="text-body-small text-(--color-text-secondary)">Asignees:</p>
<AvatarGroup
avatars={[
{ name: 'A' },
@@ -4,23 +4,23 @@ import { Card, Avatar, Badge, Button } from '../components'
export function NotificationsCard() {
return (
<Card className="w-[320px]" padding={false}>
<div className="p-4 border-b border-[var(--color-border-default)]">
<div className="p-4 border-b border-(--color-border-default)">
<div className="flex items-center justify-between">
<h3 className="text-heading-small">Notifications</h3>
<Badge variant="primary">6</Badge>
</div>
<p className="text-body-small text-[var(--color-text-tertiary)] mt-1">Unread</p>
<p className="text-body-small text-(--color-text-tertiary) mt-1">Unread</p>
</div>
<div className="divide-y divide-[var(--color-border-default)]">
<div className="divide-y divide-(--color-border-default)">
<div className="p-4 flex gap-3">
<Avatar size="sm" name="Ashlynn George" />
<div className="flex-1 min-w-0">
<p className="text-body-small">
<span className="font-semibold">Ashlynn George</span>
<span className="text-[var(--color-text-tertiary)]"> · 1h</span>
<span className="text-(--color-text-tertiary)"> · 1h</span>
</p>
<p className="text-body-small text-[var(--color-text-secondary)]">
<p className="text-body-small text-(--color-text-secondary)">
has invited you to access "Magma project"
</p>
<div className="flex gap-2 mt-2">
@@ -32,8 +32,8 @@ export function NotificationsCard() {
</Button>
</div>
</div>
<button className="p-1 hover:bg-[var(--color-background-secondary)] rounded self-start transition-colors">
<MoreVertical className="w-4 h-4 text-[var(--color-text-tertiary)]" />
<button className="p-1 hover:bg-(--color-background-secondary) rounded self-start transition-colors">
<MoreVertical className="w-4 h-4 text-(--color-text-tertiary)" />
</button>
</div>
@@ -42,19 +42,19 @@ export function NotificationsCard() {
<div className="flex-1">
<p className="text-body-small">
<span className="font-semibold">Ashlynn George</span>
<span className="text-[var(--color-text-tertiary)]"> · 1h</span>
<span className="text-(--color-text-tertiary)"> · 1h</span>
</p>
<p className="text-body-small text-[var(--color-text-secondary)]">
<p className="text-body-small text-(--color-text-secondary)">
changed status of task in "Magma project"
</p>
</div>
<button className="p-1 hover:bg-[var(--color-background-secondary)] rounded self-start transition-colors">
<MoreVertical className="w-4 h-4 text-[var(--color-text-tertiary)]" />
<button className="p-1 hover:bg-(--color-background-secondary) rounded self-start transition-colors">
<MoreVertical className="w-4 h-4 text-(--color-text-tertiary)" />
</button>
</div>
</div>
<div className="p-4 flex gap-2 border-t border-[var(--color-border-default)]">
<div className="p-4 flex gap-2 border-t border-(--color-border-default)">
<Button variant="secondary" className="flex-1" pill>Mark all as read</Button>
<Button variant="primary" className="flex-1" pill>View all</Button>
</div>
@@ -5,14 +5,14 @@ export function ProfileCard() {
return (
<Card className="w-[280px]">
<div className="flex justify-end mb-4">
<button className="p-1 hover:bg-[var(--color-background-secondary)] rounded transition-colors">
<MoreVertical className="w-5 h-5 text-[var(--color-text-tertiary)]" />
<button className="p-1 hover:bg-(--color-background-secondary) rounded transition-colors">
<MoreVertical className="w-5 h-5 text-(--color-text-tertiary)" />
</button>
</div>
<div className="flex flex-col items-center text-center">
<Avatar size="2xl" name="Christine Thompson" />
<h3 className="text-heading-large mt-4">Christine Thompson</h3>
<p className="text-body-medium text-[var(--color-text-secondary)]">Project manager</p>
<p className="text-body-medium text-(--color-text-secondary)">Project manager</p>
<div className="flex flex-wrap gap-2 mt-4 justify-center">
<Badge variant="outline">UI/UX Design</Badge>
<Badge variant="outline">Project management</Badge>
@@ -6,13 +6,13 @@ export function ProjectStatusCard() {
<Card className="w-[380px]">
<div className="flex justify-between items-start mb-4">
<ProgressCircle value={43} size="md" />
<button className="p-1 hover:bg-[var(--color-background-secondary)] rounded transition-colors">
<MoreVertical className="w-5 h-5 text-[var(--color-text-tertiary)]" />
<button className="p-1 hover:bg-(--color-background-secondary) rounded transition-colors">
<MoreVertical className="w-5 h-5 text-(--color-text-tertiary)" />
</button>
</div>
<h3 className="text-heading-large mb-2">Amber website redesign</h3>
<p className="text-body-medium text-[var(--color-text-secondary)] mb-4">
<p className="text-body-medium text-(--color-text-secondary) mb-4">
In today's fast-paced digital landscape, our mission is to transform our website into a more intuitive, engaging, and user-friendly platfor...
</p>
@@ -11,25 +11,25 @@ export function TeamMembersCard() {
return (
<Card className="w-[320px]" padding={false}>
<div className="divide-y divide-[var(--color-border-default)]">
<div className="divide-y divide-(--color-border-default)">
{members.map((member, i) => (
<div key={i} className="p-4 flex items-center gap-3">
<Avatar name={member.name} />
<div className="flex-1">
<p className="text-heading-small">{member.name}</p>
<p className="text-body-small text-[var(--color-text-secondary)]">{member.role}</p>
<p className="text-body-small text-(--color-text-secondary)">{member.role}</p>
</div>
<button className="p-1 hover:bg-[var(--color-background-secondary)] rounded transition-colors">
<MoreVertical className="w-4 h-4 text-[var(--color-text-tertiary)]" />
<button className="p-1 hover:bg-(--color-background-secondary) rounded transition-colors">
<MoreVertical className="w-4 h-4 text-(--color-text-tertiary)" />
</button>
<button className="p-2 bg-[var(--color-semantic-error-light)] text-[var(--color-semantic-error)] rounded-[var(--radius-md)] hover:opacity-80 transition-opacity">
<button className="p-2 bg-(--color-semantic-error-light) text-(--color-semantic-error) rounded-md hover:opacity-80 transition-opacity">
<MessageSquare className="w-4 h-4" />
</button>
</div>
))}
</div>
<div className="p-4 border-t border-[var(--color-border-default)] flex justify-center gap-3">
<div className="p-4 border-t border-(--color-border-default) flex justify-center gap-3">
<img src="https://upload.wikimedia.org/wikipedia/commons/b/ba/Stripe_Logo%2C_revised_2016.svg" alt="Stripe" className="h-6" />
<div className="px-3 py-1 bg-[#1A1F71] text-white text-sm font-bold rounded">VISA</div>
<div className="px-2 py-1 bg-[#003087] text-white text-xs font-bold rounded">PayPal</div>
+11 -11
View File
@@ -29,7 +29,7 @@ export function ThemeSelector({
<div className="relative">
<button
onClick={() => setIsOpen(!isOpen)}
className="flex items-center gap-2 px-3 py-2 rounded-[var(--radius-lg)] bg-[var(--color-background-secondary)] hover:bg-[var(--color-border-default)] transition-colors"
className="flex items-center gap-2 px-3 py-2 rounded-lg bg-(--color-background-secondary) hover:bg-(--color-border-default) transition-colors"
>
<div
className="w-4 h-4 rounded-full border-2 border-white shadow-sm"
@@ -37,7 +37,7 @@ export function ThemeSelector({
/>
<span className="text-body-medium font-medium">{currentTheme.name}</span>
<ChevronLeft className={cn(
"w-4 h-4 text-[var(--color-text-tertiary)] transition-transform",
"w-4 h-4 text-(--color-text-tertiary) transition-transform",
isOpen ? "rotate-90" : "-rotate-90"
)} />
</button>
@@ -48,7 +48,7 @@ export function ThemeSelector({
className="fixed inset-0 z-40"
onClick={() => setIsOpen(false)}
/>
<div className="absolute top-full right-0 mt-2 w-64 p-2 bg-[var(--color-surface-card)] rounded-[var(--radius-lg)] shadow-[var(--shadow-lg)] border border-[var(--color-border-default)] z-50">
<div className="absolute top-full right-0 mt-2 w-64 p-2 bg-(--color-surface-card) rounded-lg shadow-lg border border-(--color-border-default) z-50">
{themes.map((theme) => (
<button
key={theme.id}
@@ -57,10 +57,10 @@ export function ThemeSelector({
setIsOpen(false)
}}
className={cn(
"w-full flex items-center gap-3 px-3 py-2 rounded-[var(--radius-md)] transition-colors text-left",
"w-full flex items-center gap-3 px-3 py-2 rounded-md transition-colors text-left",
colorTheme === theme.id
? "bg-[var(--color-accent-primary-light)]"
: "hover:bg-[var(--color-background-secondary)]"
? "bg-(--color-accent-primary-light)"
: "hover:bg-(--color-background-secondary)"
)}
>
<div className="flex -space-x-1">
@@ -75,10 +75,10 @@ export function ThemeSelector({
</div>
<div className="flex-1 min-w-0">
<p className="text-body-medium font-medium">{theme.name}</p>
<p className="text-body-small text-[var(--color-text-tertiary)] truncate">{theme.description}</p>
<p className="text-body-small text-(--color-text-tertiary) truncate">{theme.description}</p>
</div>
{colorTheme === theme.id && (
<Check className="w-4 h-4 text-[var(--color-accent-primary)]" />
<Check className="w-4 h-4 text-(--color-accent-primary)" />
)}
</button>
))}
@@ -90,13 +90,13 @@ export function ThemeSelector({
{/* Light/Dark Toggle */}
<button
onClick={onModeToggle}
className="p-2 rounded-[var(--radius-lg)] bg-[var(--color-background-secondary)] hover:bg-[var(--color-border-default)] transition-colors"
className="p-2 rounded-lg bg-(--color-background-secondary) hover:bg-(--color-border-default) transition-colors"
aria-label={`Switch to ${mode === 'light' ? 'dark' : 'light'} mode`}
>
{mode === 'light' ? (
<Moon className="w-5 h-5 text-[var(--color-text-secondary)]" />
<Moon className="w-5 h-5 text-(--color-text-secondary)" />
) : (
<Sun className="w-5 h-5 text-[var(--color-text-secondary)]" />
<Sun className="w-5 h-5 text-(--color-text-secondary)" />
)}
</button>
</div>
+1
View File
@@ -21,3 +21,4 @@ jobs:
footer_title: "Auto Claude Changelog"
footer_timestamp: true
reduce_headings: true
@@ -21,6 +21,9 @@ import {
updateIdeaStatus,
dismissIdea,
dismissAllIdeas,
archiveIdea,
deleteIdea,
deleteMultipleIdeas,
startIdeationGeneration,
refreshIdeationSession,
stopIdeationGeneration,
@@ -56,6 +59,21 @@ export function registerIdeationHandlers(
dismissAllIdeas
);
ipcMain.handle(
IPC_CHANNELS.IDEATION_ARCHIVE,
archiveIdea
);
ipcMain.handle(
IPC_CHANNELS.IDEATION_DELETE,
deleteIdea
);
ipcMain.handle(
IPC_CHANNELS.IDEATION_DELETE_MULTIPLE,
deleteMultipleIdeas
);
// Generation operations
ipcMain.on(
IPC_CHANNELS.IDEATION_GENERATE,
@@ -142,3 +142,132 @@ export async function dismissAllIdeas(
};
}
}
/**
* Archive a single idea (typically when converted to task)
*/
export async function archiveIdea(
_event: IpcMainInvokeEvent,
projectId: string,
ideaId: string
): Promise<IPCResult> {
const project = projectStore.getProject(projectId);
if (!project) {
return { success: false, error: 'Project not found' };
}
const ideationPath = path.join(
project.path,
AUTO_BUILD_PATHS.IDEATION_DIR,
AUTO_BUILD_PATHS.IDEATION_FILE
);
const ideation = readIdeationFile(ideationPath);
if (!ideation) {
return { success: false, error: 'Ideation not found' };
}
try {
const idea = ideation.ideas?.find((i) => i.id === ideaId);
if (!idea) {
return { success: false, error: 'Idea not found' };
}
idea.status = 'archived';
updateIdeationTimestamp(ideation);
writeIdeationFile(ideationPath, ideation);
return { success: true };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to archive idea'
};
}
}
/**
* Delete a single idea permanently
*/
export async function deleteIdea(
_event: IpcMainInvokeEvent,
projectId: string,
ideaId: string
): Promise<IPCResult> {
const project = projectStore.getProject(projectId);
if (!project) {
return { success: false, error: 'Project not found' };
}
const ideationPath = path.join(
project.path,
AUTO_BUILD_PATHS.IDEATION_DIR,
AUTO_BUILD_PATHS.IDEATION_FILE
);
const ideation = readIdeationFile(ideationPath);
if (!ideation) {
return { success: false, error: 'Ideation not found' };
}
try {
const ideaIndex = ideation.ideas?.findIndex((i) => i.id === ideaId);
if (ideaIndex === undefined || ideaIndex === -1) {
return { success: false, error: 'Idea not found' };
}
ideation.ideas?.splice(ideaIndex, 1);
updateIdeationTimestamp(ideation);
writeIdeationFile(ideationPath, ideation);
return { success: true };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to delete idea'
};
}
}
/**
* Delete multiple ideas permanently
*/
export async function deleteMultipleIdeas(
_event: IpcMainInvokeEvent,
projectId: string,
ideaIds: string[]
): Promise<IPCResult> {
const project = projectStore.getProject(projectId);
if (!project) {
return { success: false, error: 'Project not found' };
}
const ideationPath = path.join(
project.path,
AUTO_BUILD_PATHS.IDEATION_DIR,
AUTO_BUILD_PATHS.IDEATION_FILE
);
const ideation = readIdeationFile(ideationPath);
if (!ideation) {
return { success: false, error: 'Ideation not found' };
}
try {
const idsToDelete = new Set(ideaIds);
const originalCount = ideation.ideas?.length || 0;
ideation.ideas = ideation.ideas?.filter((idea) => !idsToDelete.has(idea.id)) || [];
const deletedCount = originalCount - (ideation.ideas?.length || 0);
updateIdeationTimestamp(ideation);
writeIdeationFile(ideationPath, ideation);
return { success: true, data: { deletedCount } };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to delete ideas'
};
}
}
@@ -258,8 +258,8 @@ export async function convertIdeaToTask(
const metadataPath = path.join(specDir, 'task_metadata.json');
writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
// Update idea status
idea.status = 'converted';
// Update idea status to archived (converted ideas are archived)
idea.status = 'archived';
idea.linked_task_id = specId;
updateIdeationTimestamp(ideation);
writeIdeationFile(ideationPath, ideation);
+1
View File
@@ -339,3 +339,4 @@ export class LogService {
// Singleton instance
export const logService = new LogService();
@@ -162,3 +162,4 @@ class NotificationService {
// Export singleton instance
export const notificationService = new NotificationService();
@@ -362,3 +362,4 @@ export class PythonEnvManager extends EventEmitter {
// Singleton instance
export const pythonEnvManager = new PythonEnvManager();
@@ -602,3 +602,4 @@ export class ReleaseService extends EventEmitter {
// Export singleton instance
export const releaseService = new ReleaseService();
+9 -4
View File
@@ -4,6 +4,7 @@ import { TaskAPI, createTaskAPI } from './task-api';
import { SettingsAPI, createSettingsAPI } from './settings-api';
import { FileAPI, createFileAPI } from './file-api';
import { AgentAPI, createAgentAPI } from './agent-api';
import { IdeationAPI, createIdeationAPI } from './modules/ideation-api';
export interface ElectronAPI extends
ProjectAPI,
@@ -11,7 +12,8 @@ export interface ElectronAPI extends
TaskAPI,
SettingsAPI,
FileAPI,
AgentAPI {}
AgentAPI,
IdeationAPI {}
export const createElectronAPI = (): ElectronAPI => ({
...createProjectAPI(),
@@ -19,7 +21,8 @@ export const createElectronAPI = (): ElectronAPI => ({
...createTaskAPI(),
...createSettingsAPI(),
...createFileAPI(),
...createAgentAPI()
...createAgentAPI(),
...createIdeationAPI()
});
// Export individual API creators for potential use in tests or specialized contexts
@@ -29,7 +32,8 @@ export {
createTaskAPI,
createSettingsAPI,
createFileAPI,
createAgentAPI
createAgentAPI,
createIdeationAPI
};
export type {
@@ -38,5 +42,6 @@ export type {
TaskAPI,
SettingsAPI,
FileAPI,
AgentAPI
AgentAPI,
IdeationAPI
};
@@ -23,6 +23,9 @@ export interface IdeationAPI {
convertIdeaToTask: (projectId: string, ideaId: string) => Promise<IPCResult<Task>>;
dismissIdea: (projectId: string, ideaId: string) => Promise<IPCResult>;
dismissAllIdeas: (projectId: string) => Promise<IPCResult>;
archiveIdea: (projectId: string, ideaId: string) => Promise<IPCResult>;
deleteIdea: (projectId: string, ideaId: string) => Promise<IPCResult>;
deleteMultipleIdeas: (projectId: string, ideaIds: string[]) => Promise<IPCResult>;
// Event Listeners
onIdeationProgress: (
@@ -77,6 +80,15 @@ export const createIdeationAPI = (): IdeationAPI => ({
dismissAllIdeas: (projectId: string): Promise<IPCResult> =>
invokeIpc(IPC_CHANNELS.IDEATION_DISMISS_ALL, projectId),
archiveIdea: (projectId: string, ideaId: string): Promise<IPCResult> =>
invokeIpc(IPC_CHANNELS.IDEATION_ARCHIVE, projectId, ideaId),
deleteIdea: (projectId: string, ideaId: string): Promise<IPCResult> =>
invokeIpc(IPC_CHANNELS.IDEATION_DELETE, projectId, ideaId),
deleteMultipleIdeas: (projectId: string, ideaIds: string[]): Promise<IPCResult> =>
invokeIpc(IPC_CHANNELS.IDEATION_DELETE_MULTIPLE, projectId, ideaIds),
// Event Listeners
onIdeationProgress: (
callback: (projectId: string, status: IdeationGenerationStatus) => void
@@ -77,7 +77,7 @@ function DroppablePhaseColumn({
<div
ref={setNodeRef}
className={cn(
'flex w-72 shrink-0 flex-col rounded-xl border border-white/5 bg-linear-to-b from-secondary/30 to-transparent backdrop-blur-sm transition-all duration-200',
'flex min-w-80 w-80 shrink-0 flex-col rounded-xl border border-white/5 bg-linear-to-b from-secondary/30 to-transparent backdrop-blur-sm transition-all duration-200',
getPhaseStatusColor(phase.status),
'border-t-2',
isOver && 'drop-zone-highlight'
@@ -97,7 +97,7 @@ function DroppablePhaseColumn({
>
{phase.order}
</div>
<h2 className="font-semibold text-sm text-foreground truncate max-w-[140px]">
<h2 className="font-semibold text-sm text-foreground truncate max-w-[180px]">
{phase.name}
</h2>
<span className="column-count-badge">
@@ -330,7 +330,7 @@ export function RoadmapKanbanView({
<DragOverlay>
{activeFeature ? (
<div className="drag-overlay-card">
<Card className="p-4 w-72 shadow-2xl">
<Card className="p-4 w-80 shadow-2xl">
<div className="font-medium">{activeFeature.title}</div>
<p className="text-sm text-muted-foreground line-clamp-2 mt-1">
{activeFeature.description}
@@ -456,9 +456,9 @@ export function TaskCreationWizard({
showFileExplorer ? "sm:max-w-[900px]" : "sm:max-w-[550px]"
)}
>
<div className="flex h-full">
<div className="flex h-full min-h-0 overflow-hidden">
{/* Form content */}
<div className="flex-1 flex flex-col p-6 min-w-0 overflow-y-auto">
<div className="flex-1 flex flex-col p-6 min-w-0 min-h-0 overflow-y-auto">
<DialogHeader>
<div className="flex items-center justify-between">
<DialogTitle className="text-foreground">Create New Task</DialogTitle>
@@ -500,6 +500,7 @@ export function TaskCreationWizard({
onPaste={handlePaste}
rows={5}
disabled={isCreating}
className="resize-y min-h-[120px] max-h-[400px]"
/>
<p className="text-xs text-muted-foreground">
Tip: Paste screenshots directly with {navigator.platform.includes('Mac') ? '⌘V' : 'Ctrl+V'} to add reference images.
@@ -25,12 +25,12 @@ export function InvestigationDialog({
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Sparkles className="h-5 w-5 text-info" />
AI Investigation
Create Task from Issue
</DialogTitle>
<DialogDescription>
{selectedIssue && (
<span>
Investigating issue #{selectedIssue.number}: {selectedIssue.title}
Issue #{selectedIssue.number}: {selectedIssue.title}
</span>
)}
</DialogDescription>
@@ -39,16 +39,15 @@ export function InvestigationDialog({
{investigationStatus.phase === 'idle' ? (
<div className="space-y-4">
<p className="text-sm text-muted-foreground">
The AI will analyze this issue, examine relevant code, and create a planned task in your Kanban board.
Create a task from this GitHub issue. The task will be added to your Kanban board in the Planned column.
</p>
<div className="rounded-lg border border-border bg-muted/30 p-4">
<h4 className="text-sm font-medium mb-2">What the AI will do:</h4>
<h4 className="text-sm font-medium mb-2">The task will include:</h4>
<ul className="text-sm text-muted-foreground space-y-1">
<li>- Analyze the issue description and comments</li>
<li>- Identify affected files and components</li>
<li>- Estimate complexity and effort</li>
<li>- Create acceptance criteria</li>
<li>- Generate a task spec for implementation</li>
<li> Issue title and description</li>
<li> Link back to the GitHub issue</li>
<li> Labels and metadata from the issue</li>
<li> Ready to start when you move it to In Progress</li>
</ul>
</div>
</div>
@@ -71,7 +70,7 @@ export function InvestigationDialog({
{investigationStatus.phase === 'complete' && (
<div className="rounded-lg bg-success/10 border border-success/30 p-3 flex items-center gap-2 text-sm text-success">
<CheckCircle2 className="h-4 w-4" />
Investigation complete! Task created in Kanban board.
Task created! View it in your Kanban board.
</div>
)}
</div>
@@ -85,14 +84,14 @@ export function InvestigationDialog({
</Button>
<Button onClick={onStartInvestigation}>
<Sparkles className="h-4 w-4 mr-2" />
Start Investigation
Create Task
</Button>
</>
)}
{investigationStatus.phase !== 'idle' && investigationStatus.phase !== 'complete' && (
<Button variant="outline" disabled>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
Investigating...
Creating...
</Button>
)}
{investigationStatus.phase === 'complete' && (
@@ -79,17 +79,17 @@ export function IssueDetail({ issue, onInvestigate, investigationResult }: Issue
<div className="flex items-center gap-2">
<Button onClick={onInvestigate} className="flex-1">
<Sparkles className="h-4 w-4 mr-2" />
Investigate & Create Task
Create Task
</Button>
</div>
{/* Investigation Result */}
{/* Task Created Result */}
{investigationResult?.success && (
<Card className="bg-success/5 border-success/30">
<CardHeader className="pb-2">
<CardTitle className="text-sm flex items-center gap-2 text-success">
<CheckCircle2 className="h-4 w-4" />
Investigation Complete
Task Created
</CardTitle>
</CardHeader>
<CardContent className="text-sm space-y-2">
@@ -1,5 +1,6 @@
import { useEffect, useCallback } from 'react';
import { useGitHubStore, investigateGitHubIssue } from '../../../stores/github-store';
import { loadTasks } from '../../../stores/task-store';
import type { GitHubIssue } from '../../../../shared/types';
export function useGitHubInvestigation(projectId: string | undefined) {
@@ -27,6 +28,10 @@ export function useGitHubInvestigation(projectId: string | undefined) {
(eventProjectId, result) => {
if (eventProjectId === projectId) {
setInvestigationResult(result);
// Refresh the task store so the new task appears on the Kanban board
if (result.success && result.taskId) {
loadTasks(projectId);
}
}
}
);
@@ -1,4 +1,4 @@
import { useEffect, useCallback } from 'react';
import { useEffect, useCallback, useRef } from 'react';
import { useGitHubStore, loadGitHubIssues, checkGitHubConnection } from '../../../stores/github-store';
import type { FilterState } from '../types';
@@ -16,16 +16,29 @@ export function useGitHubIssues(projectId: string | undefined) {
getOpenIssuesCount
} = useGitHubStore();
// Load issues when project changes
// Track if we've checked connection for this mount
const hasCheckedRef = useRef(false);
// Always check connection when component mounts or projectId changes
useEffect(() => {
if (projectId) {
// Always check connection on mount (in case settings changed)
checkGitHubConnection(projectId);
hasCheckedRef.current = true;
}
}, [projectId]);
// Load issues when filter changes or after connection is established
useEffect(() => {
if (projectId && syncStatus?.connected) {
loadGitHubIssues(projectId, filterState);
}
}, [projectId, filterState]);
}, [projectId, filterState, syncStatus?.connected]);
const handleRefresh = useCallback(() => {
if (projectId) {
// Re-check connection and reload issues
checkGitHubConnection(projectId);
loadGitHubIssues(projectId, filterState);
}
}, [projectId, filterState]);
@@ -191,10 +191,12 @@ export function GenerationProgressScreen({
<IdeaCard
key={idea.id}
idea={idea}
isSelected={false}
onClick={() => onSelectIdea(selectedIdea?.id === idea.id ? null : idea)}
onConvert={onConvert}
onGoToTask={onGoToTask}
onDismiss={onDismiss}
onToggleSelect={() => {/* Selection disabled during generation */}}
/>
))}
@@ -1,8 +1,9 @@
import { ExternalLink, Play, X } from 'lucide-react';
import { ExternalLink, Play, X, Check } from 'lucide-react';
import { Button } from '../ui/button';
import { Badge } from '../ui/badge';
import { Card } from '../ui/card';
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
import { Checkbox } from '../ui/checkbox';
import {
IDEATION_TYPE_LABELS,
IDEATION_TYPE_COLORS,
@@ -36,25 +37,45 @@ import {
interface IdeaCardProps {
idea: Idea;
isSelected: boolean;
onClick: () => void;
onConvert: (idea: Idea) => void;
onGoToTask?: (taskId: string) => void;
onDismiss: (idea: Idea) => void;
onToggleSelect: (ideaId: string) => void;
}
export function IdeaCard({ idea, onClick, onConvert, onGoToTask, onDismiss }: IdeaCardProps) {
export function IdeaCard({ idea, isSelected, onClick, onConvert, onGoToTask, onDismiss, onToggleSelect }: IdeaCardProps) {
const isDismissed = idea.status === 'dismissed';
const isArchived = idea.status === 'archived';
const isConverted = idea.status === 'converted';
const isInactive = isDismissed || isArchived;
return (
<Card
className={`p-4 hover:bg-muted/50 cursor-pointer transition-colors ${
isDismissed ? 'opacity-50' : ''
}`}
isInactive ? 'opacity-50' : ''
} ${isSelected ? 'ring-2 ring-primary bg-primary/5' : ''}`}
onClick={onClick}
>
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-start gap-3">
{/* Selection checkbox */}
<div
className="pt-0.5"
onClick={(e) => {
e.stopPropagation();
onToggleSelect(idea.id);
}}
>
<Checkbox
checked={isSelected}
onCheckedChange={() => onToggleSelect(idea.id)}
className="data-[state=checked]:bg-primary data-[state=checked]:border-primary"
/>
</div>
<div className="flex-1 flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<Badge variant="outline" className={IDEATION_TYPE_COLORS[idea.type]}>
<TypeIcon type={idea.type} />
@@ -96,67 +117,91 @@ export function IdeaCard({ idea, onClick, onConvert, onGoToTask, onDismiss }: Id
</Badge>
)}
</div>
<h3 className={`font-medium ${isDismissed ? 'line-through' : ''}`}>
<h3 className={`font-medium ${isInactive ? 'line-through' : ''}`}>
{idea.title}
</h3>
<p className="text-sm text-muted-foreground line-clamp-2">{idea.description}</p>
</div>
{/* Action buttons */}
{!isInactive && !isConverted && (
<div className="flex items-center gap-1 ml-2">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0"
onClick={(e) => {
e.stopPropagation();
onConvert(idea);
}}
>
<Play className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Convert to Task</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0 text-muted-foreground hover:text-destructive"
onClick={(e) => {
e.stopPropagation();
onDismiss(idea);
}}
>
<X className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Dismiss</TooltipContent>
</Tooltip>
</div>
)}
{/* Archived ideas show link to task */}
{isArchived && idea.taskId && onGoToTask && (
<div className="flex items-center gap-1 ml-2">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0 text-primary"
onClick={(e) => {
e.stopPropagation();
onGoToTask(idea.taskId!);
}}
>
<ExternalLink className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Go to Task</TooltipContent>
</Tooltip>
</div>
)}
{/* Legacy: converted status also shows link to task */}
{isConverted && idea.taskId && onGoToTask && (
<div className="flex items-center gap-1 ml-2">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0 text-primary"
onClick={(e) => {
e.stopPropagation();
onGoToTask(idea.taskId!);
}}
>
<ExternalLink className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Go to Task</TooltipContent>
</Tooltip>
</div>
)}
</div>
{!isDismissed && !isConverted && (
<div className="flex items-center gap-1 ml-2">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0"
onClick={(e) => {
e.stopPropagation();
onConvert(idea);
}}
>
<Play className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Convert to Task</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0 text-muted-foreground hover:text-destructive"
onClick={(e) => {
e.stopPropagation();
onDismiss(idea);
}}
>
<X className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Dismiss</TooltipContent>
</Tooltip>
</div>
)}
{isConverted && idea.taskId && onGoToTask && (
<div className="flex items-center gap-1 ml-2">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0 text-primary"
onClick={(e) => {
e.stopPropagation();
onGoToTask(idea.taskId!);
}}
>
<ExternalLink className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Go to Task</TooltipContent>
</Tooltip>
</div>
)}
</div>
</Card>
);
@@ -27,6 +27,7 @@ export function Ideation({ projectId, onGoToTask }: IdeationProps) {
activeTab,
showConfigDialog,
showDismissed,
showArchived,
showEnvConfigModal,
showAddMoreDialog,
typesToAdd,
@@ -34,10 +35,12 @@ export function Ideation({ projectId, onGoToTask }: IdeationProps) {
isCheckingToken,
summary,
activeIdeas,
selectedIds,
setSelectedIdea,
setActiveTab,
setShowConfigDialog,
setShowDismissed,
setShowArchived,
setShowEnvConfigModal,
setShowAddMoreDialog,
setTypesToAdd,
@@ -46,6 +49,8 @@ export function Ideation({ projectId, onGoToTask }: IdeationProps) {
handleRefresh,
handleStop,
handleDismissAll,
handleDeleteSelected,
handleSelectAll,
handleEnvConfigured,
getAvailableTypesToAdd,
handleAddMoreIdeas,
@@ -54,6 +59,8 @@ export function Ideation({ projectId, onGoToTask }: IdeationProps) {
handleGoToTask,
handleDismiss,
toggleIdeationType,
toggleSelectIdea,
clearSelection,
getIdeasByType
} = useIdeation(projectId, { onGoToTask });
@@ -121,13 +128,19 @@ export function Ideation({ projectId, onGoToTask }: IdeationProps) {
totalIdeas={summary.totalIdeas}
ideaCountByType={summary.byType}
showDismissed={showDismissed}
showArchived={showArchived}
selectedCount={selectedIds.size}
onToggleShowDismissed={() => setShowDismissed(!showDismissed)}
onToggleShowArchived={() => setShowArchived(!showArchived)}
onOpenConfig={() => setShowConfigDialog(true)}
onOpenAddMore={() => {
setTypesToAdd([]);
setShowAddMoreDialog(true);
}}
onDismissAll={handleDismissAll}
onDeleteSelected={handleDeleteSelected}
onSelectAll={() => handleSelectAll(activeIdeas)}
onClearSelection={clearSelection}
onRefresh={handleRefresh}
hasActiveIdeas={activeIdeas.length > 0}
canAddMore={getAvailableTypesToAdd().length > 0}
@@ -143,10 +156,12 @@ export function Ideation({ projectId, onGoToTask }: IdeationProps) {
<IdeaCard
key={idea.id}
idea={idea}
isSelected={selectedIds.has(idea.id)}
onClick={() => setSelectedIdea(selectedIdea?.id === idea.id ? null : idea)}
onConvert={handleConvertToTask}
onGoToTask={handleGoToTask}
onDismiss={handleDismiss}
onToggleSelect={toggleSelectIdea}
/>
))}
{activeIdeas.length === 0 && (
@@ -158,29 +173,36 @@ export function Ideation({ projectId, onGoToTask }: IdeationProps) {
</TabsContent>
{/* Type-specific Views */}
{ALL_IDEATION_TYPES.map((type) => (
<TabsContent key={type} value={type} className="flex-1 overflow-auto p-4">
<div className="mb-4 p-3 bg-muted/50 rounded-lg">
<p className="text-sm text-muted-foreground">
{IDEATION_TYPE_DESCRIPTIONS[type]}
</p>
</div>
<div className="grid gap-3">
{getIdeasByType(type)
.filter((idea) => showDismissed || idea.status !== 'dismissed')
.map((idea) => (
{ALL_IDEATION_TYPES.map((type) => {
const typeIdeas = getIdeasByType(type).filter((idea) => {
if (!showDismissed && idea.status === 'dismissed') return false;
if (!showArchived && idea.status === 'archived') return false;
return true;
});
return (
<TabsContent key={type} value={type} className="flex-1 overflow-auto p-4">
<div className="mb-4 p-3 bg-muted/50 rounded-lg">
<p className="text-sm text-muted-foreground">
{IDEATION_TYPE_DESCRIPTIONS[type]}
</p>
</div>
<div className="grid gap-3">
{typeIdeas.map((idea) => (
<IdeaCard
key={idea.id}
idea={idea}
isSelected={selectedIds.has(idea.id)}
onClick={() => setSelectedIdea(selectedIdea?.id === idea.id ? null : idea)}
onConvert={handleConvertToTask}
onGoToTask={handleGoToTask}
onDismiss={handleDismiss}
onToggleSelect={toggleSelectIdea}
/>
))}
</div>
</TabsContent>
))}
</div>
</TabsContent>
);
})}
</IdeationFilters>
</div>
@@ -1,4 +1,4 @@
import { Lightbulb, Eye, EyeOff, Settings2, Plus, Trash2, RefreshCw } from 'lucide-react';
import { Lightbulb, Eye, EyeOff, Settings2, Plus, Trash2, RefreshCw, Archive, CheckSquare, Square, X } from 'lucide-react';
import { Button } from '../ui/button';
import { Badge } from '../ui/badge';
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
@@ -10,10 +10,16 @@ interface IdeationHeaderProps {
totalIdeas: number;
ideaCountByType: Record<string, number>;
showDismissed: boolean;
showArchived: boolean;
selectedCount: number;
onToggleShowDismissed: () => void;
onToggleShowArchived: () => void;
onOpenConfig: () => void;
onOpenAddMore: () => void;
onDismissAll: () => void;
onDeleteSelected: () => void;
onSelectAll: () => void;
onClearSelection: () => void;
onRefresh: () => void;
hasActiveIdeas: boolean;
canAddMore: boolean;
@@ -23,14 +29,21 @@ export function IdeationHeader({
totalIdeas,
ideaCountByType,
showDismissed,
showArchived,
selectedCount,
onToggleShowDismissed,
onToggleShowArchived,
onOpenConfig,
onOpenAddMore,
onDismissAll,
onDeleteSelected,
onSelectAll,
onClearSelection,
onRefresh,
hasActiveIdeas,
canAddMore
}: IdeationHeaderProps) {
const hasSelection = selectedCount > 0;
return (
<div className="shrink-0 border-b border-border p-4 bg-card/50">
<div className="flex items-start justify-between">
@@ -45,10 +58,62 @@ export function IdeationHeader({
</p>
</div>
<div className="flex items-center gap-2">
{/* Selection controls */}
{hasSelection ? (
<>
<Badge variant="secondary" className="mr-1">
{selectedCount} selected
</Badge>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="sm"
className="text-destructive hover:bg-destructive hover:text-destructive-foreground"
onClick={onDeleteSelected}
>
<Trash2 className="h-4 w-4 mr-1" />
Delete
</Button>
</TooltipTrigger>
<TooltipContent>Delete selected ideas</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
onClick={onClearSelection}
>
<X className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Clear selection</TooltipContent>
</Tooltip>
<div className="w-px h-6 bg-border mx-1" />
</>
) : (
hasActiveIdeas && (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
onClick={onSelectAll}
>
<CheckSquare className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>Select all</TooltipContent>
</Tooltip>
)
)}
{/* View toggles */}
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
variant={showDismissed ? 'secondary' : 'outline'}
size="icon"
onClick={onToggleShowDismissed}
>
@@ -59,6 +124,20 @@ export function IdeationHeader({
{showDismissed ? 'Hide dismissed' : 'Show dismissed'}
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant={showArchived ? 'secondary' : 'outline'}
size="icon"
onClick={onToggleShowArchived}
>
<Archive className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
{showArchived ? 'Hide archived' : 'Show archived'}
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
@@ -85,7 +164,7 @@ export function IdeationHeader({
<TooltipContent>Add more ideation types</TooltipContent>
</Tooltip>
)}
{hasActiveIdeas && (
{hasActiveIdeas && !hasSelection && (
<Tooltip>
<TooltipTrigger asChild>
<Button
@@ -7,8 +7,10 @@ import {
stopIdeation,
appendIdeation,
dismissAllIdeasForProject,
deleteMultipleIdeasForProject,
getIdeasByType,
getActiveIdeas,
getArchivedIdeas,
getIdeationSummary,
setupIdeationListeners
} from '../../../stores/ideation-store';
@@ -29,11 +31,16 @@ export function useIdeation(projectId: string, options: UseIdeationOptions = {})
const setConfig = useIdeationStore((state) => state.setConfig);
const logs = useIdeationStore((state) => state.logs);
const typeStates = useIdeationStore((state) => state.typeStates);
const selectedIds = useIdeationStore((state) => state.selectedIds);
const toggleSelectIdea = useIdeationStore((state) => state.toggleSelectIdea);
const selectAllIdeas = useIdeationStore((state) => state.selectAllIdeas);
const clearSelection = useIdeationStore((state) => state.clearSelection);
const [selectedIdea, setSelectedIdea] = useState<Idea | null>(null);
const [activeTab, setActiveTab] = useState<string>('all');
const [showConfigDialog, setShowConfigDialog] = useState(false);
const [showDismissed, setShowDismissed] = useState(false);
const [showArchived, setShowArchived] = useState(false);
const [showEnvConfigModal, setShowEnvConfigModal] = useState(false);
const [pendingAction, setPendingAction] = useState<'generate' | 'refresh' | 'append' | null>(null);
const [showAddMoreDialog, setShowAddMoreDialog] = useState(false);
@@ -149,8 +156,39 @@ export function useIdeation(projectId: string, options: UseIdeationOptions = {})
}
};
const handleDeleteSelected = async () => {
if (selectedIds.size === 0) return;
await deleteMultipleIdeasForProject(projectId, Array.from(selectedIds));
};
const handleSelectAll = useCallback((ideas: Idea[]) => {
selectAllIdeas(ideas.map(idea => idea.id));
}, [selectAllIdeas]);
const summary = getIdeationSummary(session);
const activeIdeas = showDismissed ? session?.ideas || [] : getActiveIdeas(session);
const archivedIdeas = getArchivedIdeas(session);
// Filter ideas based on visibility settings
const getFilteredIdeas = useCallback(() => {
if (!session) return [];
let ideas = session.ideas;
// Start with base filtering (exclude dismissed and archived by default)
if (!showDismissed && !showArchived) {
ideas = getActiveIdeas(session);
} else if (showDismissed && !showArchived) {
// Show dismissed but not archived
ideas = ideas.filter(idea => idea.status !== 'archived');
} else if (!showDismissed && showArchived) {
// Show archived but not dismissed
ideas = ideas.filter(idea => idea.status !== 'dismissed');
}
// If both are true, show all
return ideas;
}, [session, showDismissed, showArchived]);
const activeIdeas = getFilteredIdeas();
return {
// State
@@ -163,6 +201,7 @@ export function useIdeation(projectId: string, options: UseIdeationOptions = {})
activeTab,
showConfigDialog,
showDismissed,
showArchived,
showEnvConfigModal,
showAddMoreDialog,
typesToAdd,
@@ -170,12 +209,15 @@ export function useIdeation(projectId: string, options: UseIdeationOptions = {})
isCheckingToken,
summary,
activeIdeas,
archivedIdeas,
selectedIds,
// Actions
setSelectedIdea,
setActiveTab,
setShowConfigDialog,
setShowDismissed,
setShowArchived,
setShowEnvConfigModal,
setShowAddMoreDialog,
setTypesToAdd,
@@ -184,6 +226,8 @@ export function useIdeation(projectId: string, options: UseIdeationOptions = {})
handleRefresh,
handleStop,
handleDismissAll,
handleDeleteSelected,
handleSelectAll,
handleEnvConfigured,
getAvailableTypesToAdd,
handleAddMoreIdeas,
@@ -192,6 +236,8 @@ export function useIdeation(projectId: string, options: UseIdeationOptions = {})
handleGoToTask,
handleDismiss,
toggleIdeationType,
toggleSelectIdea,
clearSelection,
// Helper functions
getIdeasByType: (type: IdeationType) => getIdeasByType(session, type)
@@ -5,6 +5,7 @@ import {
initializeProject,
updateProjectAutoBuild
} from '../../../stores/project-store';
import { checkGitHubConnection as checkGitHubConnectionGlobal } from '../../../stores/github-store';
import type {
Project,
ProjectSettings as ProjectSettingsType,
@@ -34,7 +35,7 @@ export interface UseProjectSettingsReturn {
envError: string | null;
setEnvError: React.Dispatch<React.SetStateAction<string | null>>;
isSavingEnv: boolean;
updateEnvConfig: (updates: Partial<ProjectEnvConfig>) => void;
updateEnvConfig: (updates: Partial<ProjectEnvConfig>) => Promise<void>;
// Password visibility toggles
showClaudeToken: boolean;
@@ -210,6 +211,7 @@ export function useProjectSettings(
}, [envConfig?.linearEnabled, envConfig?.linearApiKey, project.id]);
// Check GitHub connection when token/repo changes
// Also updates the global GitHub store so other components (like GitHub Issues) see the change
useEffect(() => {
const checkGitHubConnection = async () => {
if (!envConfig?.githubEnabled || !envConfig.githubToken || !envConfig.githubRepo) {
@@ -219,9 +221,11 @@ export function useProjectSettings(
setIsCheckingGitHub(true);
try {
const result = await window.electronAPI.checkGitHubConnection(project.id);
if (result.success && result.data) {
setGitHubConnectionStatus(result.data);
// Use the global store action - it makes the API call AND updates the global store
// This ensures the GitHub Issues page sees the updated status
const status = await checkGitHubConnectionGlobal(project.id);
if (status) {
setGitHubConnectionStatus(status);
}
} catch {
setGitHubConnectionStatus({ connected: false, error: 'Failed to check connection' });
@@ -341,9 +345,22 @@ export function useProjectSettings(
}
};
const updateEnvConfig = (updates: Partial<ProjectEnvConfig>) => {
const updateEnvConfig = async (updates: Partial<ProjectEnvConfig>) => {
if (envConfig) {
setEnvConfig({ ...envConfig, ...updates });
const newConfig = { ...envConfig, ...updates };
// Save to backend FIRST so disk is updated before effects run
try {
const result = await window.electronAPI.updateProjectEnv(project.id, newConfig);
if (!result.success) {
console.error('[useProjectSettings] Failed to auto-save env config:', result.error);
}
} catch (err) {
console.error('[useProjectSettings] Error auto-saving env config:', err);
}
// Then update local state (triggers effects that read from disk)
setEnvConfig(newConfig);
}
};
@@ -103,6 +103,12 @@ export const infrastructureMock = {
dismissAllIdeas: async () => ({ success: true }),
archiveIdea: async () => ({ success: true }),
deleteIdea: async () => ({ success: true }),
deleteMultipleIdeas: async () => ({ success: true }),
onIdeationProgress: () => () => {},
onIdeationLog: () => () => {},
onIdeationComplete: () => () => {},
@@ -21,6 +21,8 @@ interface IdeationState {
logs: string[];
// Track which ideation types are pending, generating, completed, or failed
typeStates: Record<IdeationType, IdeationTypeState>;
// Selection state
selectedIds: Set<string>;
// Actions
setSession: (session: IdeationSession | null) => void;
@@ -30,9 +32,16 @@ interface IdeationState {
setIdeaTaskId: (ideaId: string, taskId: string) => void;
dismissIdea: (ideaId: string) => void;
dismissAllIdeas: () => void;
archiveIdea: (ideaId: string) => void;
deleteIdea: (ideaId: string) => void;
deleteMultipleIdeas: (ideaIds: string[]) => void;
clearSession: () => void;
addLog: (log: string) => void;
clearLogs: () => void;
// Selection actions
toggleSelectIdea: (ideaId: string) => void;
selectAllIdeas: (ideaIds: string[]) => void;
clearSelection: () => void;
// New actions for streaming parallel results
initializeTypeStates: (types: IdeationType[]) => void;
setTypeState: (type: IdeationType, state: IdeationTypeState) => void;
@@ -70,6 +79,7 @@ export const useIdeationStore = create<IdeationState>((set) => ({
config: initialConfig,
logs: [],
typeStates: { ...initialTypeStates },
selectedIds: new Set<string>(),
// Actions
setSession: (session) => set({ session }),
@@ -104,7 +114,7 @@ export const useIdeationStore = create<IdeationState>((set) => ({
const updatedIdeas = state.session.ideas.map((idea) =>
idea.id === ideaId
? { ...idea, taskId, status: 'converted' as IdeationStatus }
? { ...idea, taskId, status: 'archived' as IdeationStatus }
: idea
);
@@ -139,7 +149,7 @@ export const useIdeationStore = create<IdeationState>((set) => ({
if (!state.session) return state;
const updatedIdeas = state.session.ideas.map((idea) =>
idea.status !== 'dismissed' && idea.status !== 'converted'
idea.status !== 'dismissed' && idea.status !== 'converted' && idea.status !== 'archived'
? { ...idea, status: 'dismissed' as IdeationStatus }
: idea
);
@@ -153,11 +163,70 @@ export const useIdeationStore = create<IdeationState>((set) => ({
};
}),
archiveIdea: (ideaId) =>
set((state) => {
if (!state.session) return state;
const updatedIdeas = state.session.ideas.map((idea) =>
idea.id === ideaId ? { ...idea, status: 'archived' as IdeationStatus } : idea
);
return {
session: {
...state.session,
ideas: updatedIdeas,
updatedAt: new Date()
}
};
}),
deleteIdea: (ideaId) =>
set((state) => {
if (!state.session) return state;
const updatedIdeas = state.session.ideas.filter((idea) => idea.id !== ideaId);
// Also remove from selection if selected
const newSelectedIds = new Set(state.selectedIds);
newSelectedIds.delete(ideaId);
return {
session: {
...state.session,
ideas: updatedIdeas,
updatedAt: new Date()
},
selectedIds: newSelectedIds
};
}),
deleteMultipleIdeas: (ideaIds) =>
set((state) => {
if (!state.session) return state;
const idsToDelete = new Set(ideaIds);
const updatedIdeas = state.session.ideas.filter((idea) => !idsToDelete.has(idea.id));
// Clear selection for deleted items
const newSelectedIds = new Set(state.selectedIds);
ideaIds.forEach((id) => newSelectedIds.delete(id));
return {
session: {
...state.session,
ideas: updatedIdeas,
updatedAt: new Date()
},
selectedIds: newSelectedIds
};
}),
clearSession: () =>
set({
session: null,
generationStatus: initialGenerationStatus,
typeStates: { ...initialTypeStates }
typeStates: { ...initialTypeStates },
selectedIds: new Set<string>()
}),
addLog: (log) =>
@@ -167,6 +236,28 @@ export const useIdeationStore = create<IdeationState>((set) => ({
clearLogs: () => set({ logs: [] }),
// Selection actions
toggleSelectIdea: (ideaId) =>
set((state) => {
const newSelectedIds = new Set(state.selectedIds);
if (newSelectedIds.has(ideaId)) {
newSelectedIds.delete(ideaId);
} else {
newSelectedIds.add(ideaId);
}
return { selectedIds: newSelectedIds };
}),
selectAllIdeas: (ideaIds) =>
set(() => ({
selectedIds: new Set(ideaIds)
})),
clearSelection: () =>
set(() => ({
selectedIds: new Set<string>()
})),
// Initialize type states when starting generation
initializeTypeStates: (types) =>
set((state) => {
@@ -301,6 +392,37 @@ export async function dismissAllIdeasForProject(projectId: string): Promise<bool
return result.success;
}
export async function archiveIdeaForProject(projectId: string, ideaId: string): Promise<boolean> {
const store = useIdeationStore.getState();
const result = await window.electronAPI.archiveIdea(projectId, ideaId);
if (result.success) {
store.archiveIdea(ideaId);
store.addLog('Idea archived');
}
return result.success;
}
export async function deleteIdeaForProject(projectId: string, ideaId: string): Promise<boolean> {
const store = useIdeationStore.getState();
const result = await window.electronAPI.deleteIdea(projectId, ideaId);
if (result.success) {
store.deleteIdea(ideaId);
store.addLog('Idea deleted');
}
return result.success;
}
export async function deleteMultipleIdeasForProject(projectId: string, ideaIds: string[]): Promise<boolean> {
const store = useIdeationStore.getState();
const result = await window.electronAPI.deleteMultipleIdeas(projectId, ideaIds);
if (result.success) {
store.deleteMultipleIdeas(ideaIds);
store.clearSelection();
store.addLog(`${ideaIds.length} ideas deleted`);
}
return result.success;
}
/**
* Append new ideation types to existing session without clearing existing ideas.
* This allows users to add more categories (like security, performance) while keeping
@@ -356,7 +478,12 @@ export function getIdeasByStatus(
export function getActiveIdeas(session: IdeationSession | null): Idea[] {
if (!session) return [];
return session.ideas.filter((idea) => idea.status !== 'dismissed');
return session.ideas.filter((idea) => idea.status !== 'dismissed' && idea.status !== 'archived');
}
export function getArchivedIdeas(session: IdeationSession | null): Idea[] {
if (!session) return [];
return session.ideas.filter((idea) => idea.status === 'archived');
}
export function getIdeationSummary(session: IdeationSession | null): IdeationSummary {
@@ -211,3 +211,4 @@ export function canCreateRelease(): boolean {
!store.isCreatingRelease
);
}
@@ -56,7 +56,8 @@ export const IDEATION_STATUS_COLORS: Record<string, string> = {
draft: 'bg-muted text-muted-foreground',
selected: 'bg-primary/10 text-primary',
converted: 'bg-success/10 text-success',
dismissed: 'bg-destructive/10 text-destructive line-through'
dismissed: 'bg-destructive/10 text-destructive line-through',
archived: 'bg-violet-500/10 text-violet-400'
};
// ============================================
@@ -145,6 +145,9 @@ export const IPC_CHANNELS = {
IDEATION_CONVERT_TO_TASK: 'ideation:convertToTask',
IDEATION_DISMISS: 'ideation:dismiss',
IDEATION_DISMISS_ALL: 'ideation:dismissAll',
IDEATION_ARCHIVE: 'ideation:archive',
IDEATION_DELETE: 'ideation:delete',
IDEATION_DELETE_MULTIPLE: 'ideation:deleteMultiple',
// Ideation events (main -> renderer)
IDEATION_PROGRESS: 'ideation:progress',
+1 -1
View File
@@ -17,7 +17,7 @@ export type IdeationType =
| 'security_hardening'
| 'performance_optimizations'
| 'code_quality';
export type IdeationStatus = 'draft' | 'selected' | 'converted' | 'dismissed';
export type IdeationStatus = 'draft' | 'selected' | 'converted' | 'dismissed' | 'archived';
export type IdeationGenerationPhase = 'idle' | 'analyzing' | 'discovering' | 'generating' | 'complete' | 'error';
export interface IdeationConfig {
+3
View File
@@ -336,6 +336,9 @@ export interface ElectronAPI {
convertIdeaToTask: (projectId: string, ideaId: string) => Promise<IPCResult<Task>>;
dismissIdea: (projectId: string, ideaId: string) => Promise<IPCResult>;
dismissAllIdeas: (projectId: string) => Promise<IPCResult>;
archiveIdea: (projectId: string, ideaId: string) => Promise<IPCResult>;
deleteIdea: (projectId: string, ideaId: string) => Promise<IPCResult>;
deleteMultipleIdeas: (projectId: string, ideaIds: string[]) => Promise<IPCResult>;
// Ideation event listeners
onIdeationProgress: (