ideation improvements and linting
This commit is contained in:
+60
-60
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)}>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
+11
-12
@@ -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)
|
||||
|
||||
+23
-6
@@ -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',
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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: (
|
||||
|
||||
Reference in New Issue
Block a user