refactor(FN-1364): extract modal inline styles to CSS classes
- Add CSS classes in styles.css for planning and breakdown modal styling - Refactor MissionInterviewModal to use CSS classes instead of inline styles (164 lines reduced) - Refactor PlanningModeModal to use CSS classes (67 lines reduced) - Refactor SubtaskBreakdownModal to use CSS classes (26 lines reduced) - Improve maintainability and consistency of modal styling
This commit is contained in:
@@ -584,7 +584,7 @@ export function MissionInterviewModal({
|
||||
<div className="modal modal-lg planning-modal">
|
||||
<div className="modal-header">
|
||||
<div className="detail-title-row">
|
||||
<Target size={20} style={{ color: "var(--triage)" }} />
|
||||
<Target size={20} className="icon-triage" />
|
||||
<h3>Plan Mission with AI</h3>
|
||||
</div>
|
||||
<div className="modal-header-actions">
|
||||
@@ -617,7 +617,7 @@ export function MissionInterviewModal({
|
||||
<div className="planning-initial">
|
||||
<div className="planning-view-scroll">
|
||||
<div className="planning-intro">
|
||||
<Sparkles size={32} style={{ color: "var(--triage)", marginBottom: "12px" }} />
|
||||
<Sparkles size={32} className="icon-triage-lg" />
|
||||
<h4>Transform your vision into a structured mission</h4>
|
||||
<p className="text-muted">
|
||||
Describe what you want to build. The AI will interview you to understand scope,
|
||||
@@ -667,7 +667,7 @@ export function MissionInterviewModal({
|
||||
onClick={() => handleStartInterview()}
|
||||
disabled={!missionGoal.trim()}
|
||||
>
|
||||
<Target size={16} style={{ marginRight: "8px" }} />
|
||||
<Target size={16} className="icon-mr-8" />
|
||||
Start Interview
|
||||
</button>
|
||||
</div>
|
||||
@@ -676,7 +676,7 @@ export function MissionInterviewModal({
|
||||
|
||||
{view.type === "loading" && (
|
||||
<div className="planning-loading">
|
||||
<Loader2 size={40} className="spin" style={{ color: "var(--todo)" }} />
|
||||
<Loader2 size={40} className="spin icon-todo" />
|
||||
<p>{streamingOutput ? "AI is thinking..." : "Preparing next question..."}</p>
|
||||
<div className="planning-thinking-container">
|
||||
<button
|
||||
@@ -708,21 +708,13 @@ export function MissionInterviewModal({
|
||||
<div
|
||||
className="ai-error-panel"
|
||||
role="alert"
|
||||
style={{
|
||||
border: "1px solid var(--color-error, #dc2626)",
|
||||
borderRadius: "10px",
|
||||
background: "color-mix(in srgb, var(--color-error, #dc2626) 10%, transparent)",
|
||||
padding: "14px",
|
||||
display: "grid",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
<div className="ai-error-icon" style={{ fontSize: "20px" }}>⚠️</div>
|
||||
<div className="ai-error-icon">⚠️</div>
|
||||
<div className="ai-error-message">{view.errorMessage}</div>
|
||||
<div className="ai-error-actions" style={{ display: "flex", gap: "8px" }}>
|
||||
<div className="ai-error-actions">
|
||||
<button className="btn btn-primary" onClick={() => void handleRetryFromError()} disabled={isRetrying}>
|
||||
{isRetrying ? <Loader2 size={14} className="spin" /> : <RefreshCw size={14} />}
|
||||
<span style={{ marginLeft: "6px" }}>{isRetrying ? "Retrying..." : "Retry"}</span>
|
||||
<span className="icon-ml-6">{isRetrying ? "Retrying..." : "Retry"}</span>
|
||||
</button>
|
||||
<button className="btn" onClick={handleCancel} disabled={isRetrying}>Cancel</button>
|
||||
</div>
|
||||
@@ -960,7 +952,7 @@ function InterviewQuestionForm({ question, progress, historyEntries, onSubmit }:
|
||||
disabled={!isValid()}
|
||||
>
|
||||
Continue
|
||||
<ArrowRight size={16} style={{ marginLeft: "4px" }} />
|
||||
<ArrowRight size={16} className="icon-ml-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1093,7 +1085,7 @@ function MissionPlanReview({
|
||||
)}
|
||||
|
||||
<div className="planning-summary-header">
|
||||
<CheckCircle size={24} style={{ color: "var(--color-success)" }} />
|
||||
<CheckCircle size={24} className="icon-success" />
|
||||
<h4>Mission Plan Ready</h4>
|
||||
<p className="text-muted">
|
||||
{summary.milestones.length} milestones, {totalFeatures} features. Review and edit before approving.
|
||||
@@ -1124,76 +1116,61 @@ function MissionPlanReview({
|
||||
{/* Milestones hierarchy */}
|
||||
<div className="form-group">
|
||||
<label>Roadmap</label>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
||||
<div className="roadmap-list">
|
||||
{summary.milestones.map((milestone, mi) => (
|
||||
<div
|
||||
key={mi}
|
||||
style={{
|
||||
border: "1px solid var(--border-primary)",
|
||||
borderRadius: "8px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
className="roadmap-card"
|
||||
>
|
||||
{/* Milestone header */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "8px",
|
||||
padding: "10px 12px",
|
||||
background: "var(--bg-secondary)",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
className="roadmap-card-header"
|
||||
onClick={() => toggleMilestone(mi)}
|
||||
>
|
||||
{expandedMilestones.has(mi) ? (
|
||||
<ChevronDown size={16} style={{ color: "var(--text-secondary)", flexShrink: 0 }} />
|
||||
<ChevronDown size={16} className="icon-text-secondary" />
|
||||
) : (
|
||||
<ChevronRight size={16} style={{ color: "var(--text-secondary)", flexShrink: 0 }} />
|
||||
<ChevronRight size={16} className="icon-text-secondary" />
|
||||
)}
|
||||
<Layers size={16} style={{ color: "var(--icon-milestone)", flexShrink: 0 }} />
|
||||
<Layers size={16} className="icon-milestone" />
|
||||
<input
|
||||
type="text"
|
||||
className="form-input"
|
||||
style={{ flex: 1, padding: "4px 8px", fontSize: "13px", fontWeight: 600 }}
|
||||
className="form-input roadmap-input-title"
|
||||
value={milestone.title}
|
||||
onChange={(e) => updateMilestone(mi, { title: e.target.value })}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
{summary.milestones.length > 1 && (
|
||||
<button
|
||||
className="btn-icon"
|
||||
style={{ flexShrink: 0 }}
|
||||
className="btn-icon roadmap-shrink"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
removeMilestone(mi);
|
||||
}}
|
||||
title="Remove milestone"
|
||||
>
|
||||
<Trash2 size={14} style={{ color: "var(--text-secondary)" }} />
|
||||
<Trash2 size={14} className="icon-text-secondary" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{expandedMilestones.has(mi) && (
|
||||
<div style={{ padding: "0 12px 12px 36px" }}>
|
||||
<div className="roadmap-card-body">
|
||||
<textarea
|
||||
className="planning-textarea"
|
||||
className="planning-textarea roadmap-textarea-md"
|
||||
rows={2}
|
||||
placeholder="Milestone description..."
|
||||
style={{ marginTop: "8px", fontSize: "12px" }}
|
||||
value={milestone.description || ""}
|
||||
onChange={(e) => updateMilestone(mi, { description: e.target.value })}
|
||||
/>
|
||||
<div style={{ marginTop: "6px" }}>
|
||||
<label style={{ fontSize: "11px", color: "var(--text-secondary)", fontWeight: 500 }}>
|
||||
<div className="roadmap-field-group">
|
||||
<label className="roadmap-field-label">
|
||||
Verification Criteria
|
||||
</label>
|
||||
<textarea
|
||||
className="planning-textarea"
|
||||
className="planning-textarea roadmap-textarea-sm"
|
||||
rows={2}
|
||||
placeholder="How to confirm this milestone is complete..."
|
||||
style={{ fontSize: "12px", marginTop: "2px" }}
|
||||
value={milestone.verification || ""}
|
||||
onChange={(e) => updateMilestone(mi, { verification: e.target.value })}
|
||||
/>
|
||||
@@ -1205,65 +1182,50 @@ function MissionPlanReview({
|
||||
return (
|
||||
<div
|
||||
key={si}
|
||||
style={{
|
||||
marginTop: "8px",
|
||||
border: "1px solid var(--border-primary)",
|
||||
borderRadius: "6px",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
className="roadmap-slice-card"
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "6px",
|
||||
padding: "8px 10px",
|
||||
background: "var(--bg-tertiary)",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
className="roadmap-slice-header"
|
||||
onClick={() => toggleSlice(sliceKey)}
|
||||
>
|
||||
{expandedSlices.has(sliceKey) ? (
|
||||
<ChevronDown size={14} style={{ color: "var(--text-secondary)", flexShrink: 0 }} />
|
||||
<ChevronDown size={14} className="icon-text-secondary" />
|
||||
) : (
|
||||
<ChevronRight size={14} style={{ color: "var(--text-secondary)", flexShrink: 0 }} />
|
||||
<ChevronRight size={14} className="icon-text-secondary" />
|
||||
)}
|
||||
<Package size={14} style={{ color: "var(--icon-slice)", flexShrink: 0 }} />
|
||||
<Package size={14} className="icon-slice" />
|
||||
<input
|
||||
type="text"
|
||||
className="form-input"
|
||||
style={{ flex: 1, padding: "3px 6px", fontSize: "12px", fontWeight: 500 }}
|
||||
className="form-input roadmap-input-subtitle"
|
||||
value={slice.title}
|
||||
onChange={(e) => updateSlice(mi, si, { title: e.target.value })}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
{milestone.slices.length > 1 && (
|
||||
<button
|
||||
className="btn-icon"
|
||||
style={{ flexShrink: 0 }}
|
||||
className="btn-icon roadmap-shrink"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
removeSlice(mi, si);
|
||||
}}
|
||||
title="Remove slice"
|
||||
>
|
||||
<Trash2 size={12} style={{ color: "var(--text-secondary)" }} />
|
||||
<Trash2 size={12} className="icon-text-secondary" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{expandedSlices.has(sliceKey) && (
|
||||
<div style={{ padding: "8px 10px 10px 30px" }}>
|
||||
<div className="roadmap-slice-body">
|
||||
{/* Slice verification */}
|
||||
<div style={{ marginBottom: "8px" }}>
|
||||
<label style={{ fontSize: "11px", color: "var(--text-secondary)", fontWeight: 500 }}>
|
||||
<div className="roadmap-slice-field-group">
|
||||
<label className="roadmap-field-label">
|
||||
Slice Verification
|
||||
</label>
|
||||
<textarea
|
||||
className="planning-textarea"
|
||||
className="planning-textarea roadmap-textarea-xs"
|
||||
rows={1}
|
||||
placeholder="How to confirm this slice is done..."
|
||||
style={{ fontSize: "11px", marginTop: "2px" }}
|
||||
value={slice.verification || ""}
|
||||
onChange={(e) => updateSlice(mi, si, { verification: e.target.value })}
|
||||
/>
|
||||
@@ -1272,73 +1234,41 @@ function MissionPlanReview({
|
||||
{slice.features.map((feature, fi) => (
|
||||
<div
|
||||
key={fi}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "flex-start",
|
||||
gap: "6px",
|
||||
padding: "6px 0",
|
||||
borderBottom:
|
||||
fi < slice.features.length - 1
|
||||
? "1px solid var(--border-primary)"
|
||||
: "none",
|
||||
}}
|
||||
className="roadmap-feature-row"
|
||||
>
|
||||
<Box size={12} style={{ color: "var(--icon-feature)", marginTop: "4px", flexShrink: 0 }} />
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<Box size={12} className="icon-feature" />
|
||||
<div className="roadmap-feature-content">
|
||||
<input
|
||||
type="text"
|
||||
className="form-input"
|
||||
style={{ width: "100%", padding: "2px 6px", fontSize: "12px" }}
|
||||
className="form-input roadmap-input-feature"
|
||||
value={feature.title}
|
||||
onChange={(e) =>
|
||||
updateFeature(mi, si, fi, { title: e.target.value })
|
||||
}
|
||||
/>
|
||||
{feature.description && (
|
||||
<p
|
||||
style={{
|
||||
fontSize: "11px",
|
||||
color: "var(--text-secondary)",
|
||||
margin: "2px 0 0 6px",
|
||||
}}
|
||||
>
|
||||
<p className="roadmap-feature-text">
|
||||
{feature.description}
|
||||
</p>
|
||||
)}
|
||||
{feature.acceptanceCriteria && (
|
||||
<p
|
||||
style={{
|
||||
fontSize: "11px",
|
||||
color: "var(--text-secondary)",
|
||||
margin: "2px 0 0 6px",
|
||||
fontStyle: "italic",
|
||||
}}
|
||||
>
|
||||
<p className="roadmap-feature-text--italic">
|
||||
AC: {feature.acceptanceCriteria}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
className="btn-icon"
|
||||
style={{ flexShrink: 0 }}
|
||||
className="btn-icon roadmap-shrink"
|
||||
onClick={() => removeFeature(mi, si, fi)}
|
||||
title="Remove feature"
|
||||
>
|
||||
<Trash2 size={12} style={{ color: "var(--text-secondary)" }} />
|
||||
<Trash2 size={12} className="icon-text-secondary" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<button
|
||||
className="btn"
|
||||
style={{
|
||||
fontSize: "11px",
|
||||
padding: "4px 8px",
|
||||
marginTop: "6px",
|
||||
gap: "4px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
}}
|
||||
className="btn roadmap-add-feature-btn"
|
||||
onClick={() => addFeature(mi, si)}
|
||||
>
|
||||
<Plus size={12} />
|
||||
@@ -1360,7 +1290,7 @@ function MissionPlanReview({
|
||||
|
||||
<div className="planning-actions planning-summary-actions">
|
||||
<button className="btn" onClick={onStartOver} disabled={isCreating}>
|
||||
<ArrowLeft size={16} style={{ marginRight: "4px" }} />
|
||||
<ArrowLeft size={16} className="icon-mr-4" />
|
||||
Start Over
|
||||
</button>
|
||||
<button
|
||||
@@ -1370,12 +1300,12 @@ function MissionPlanReview({
|
||||
>
|
||||
{isCreating ? (
|
||||
<>
|
||||
<Loader2 size={16} className="spin" style={{ marginRight: "8px" }} />
|
||||
<Loader2 size={16} className="spin icon-mr-8" />
|
||||
Creating Mission...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CheckCircle size={16} style={{ marginRight: "8px" }} />
|
||||
<CheckCircle size={16} className="icon-mr-8" />
|
||||
Approve Plan
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -678,7 +678,7 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
||||
<div className="modal modal-lg planning-modal">
|
||||
<div className="modal-header">
|
||||
<div className="detail-title-row">
|
||||
<Lightbulb size={20} style={{ color: "var(--triage)" }} />
|
||||
<Lightbulb size={20} className="icon-triage" />
|
||||
<h3>Planning Mode</h3>
|
||||
</div>
|
||||
<div className="modal-header-actions">
|
||||
@@ -711,7 +711,7 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
||||
<div className="planning-initial">
|
||||
<div className="planning-view-scroll">
|
||||
<div className="planning-intro">
|
||||
<Sparkles size={32} style={{ color: "var(--triage)", marginBottom: "12px" }} />
|
||||
<Sparkles size={32} className="icon-triage-lg" />
|
||||
<h4>Transform your idea into a detailed task</h4>
|
||||
<p className="text-muted">
|
||||
Describe what you want to build in plain language. The AI will ask clarifying
|
||||
@@ -757,7 +757,7 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
||||
<label htmlFor="planning-modal-model" className="form-label">
|
||||
Planning Model
|
||||
{modelsLoading && (
|
||||
<span className="text-muted" style={{ marginLeft: "8px", fontSize: "12px" }}>
|
||||
<span className="text-muted text-muted-sm">
|
||||
Loading models…
|
||||
</span>
|
||||
)}
|
||||
@@ -779,28 +779,20 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
||||
onToggleModelFavorite={handleToggleFavoriteModel}
|
||||
/>
|
||||
{modelsError && (
|
||||
<div className="form-hint" style={{ marginTop: "6px", fontSize: "12px", color: "var(--color-error)" }}>
|
||||
<div className="form-hint form-hint-error">
|
||||
{modelsError}{" "}
|
||||
<button
|
||||
type="button"
|
||||
className="text-link-btn"
|
||||
onClick={() => {
|
||||
void loadModels();
|
||||
}}
|
||||
style={{
|
||||
fontSize: "12px",
|
||||
background: "none",
|
||||
border: "none",
|
||||
color: "inherit",
|
||||
textDecoration: "underline",
|
||||
cursor: "pointer",
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div className="model-selector-current" style={{ marginTop: "8px" }}>
|
||||
<div className="model-selector-current model-selector-current--spaced">
|
||||
<span
|
||||
className={`model-badge ${
|
||||
planningModelProvider && planningModelId
|
||||
@@ -820,7 +812,7 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
||||
onClick={() => handleStartPlanning()}
|
||||
disabled={!initialPlan.trim()}
|
||||
>
|
||||
<Lightbulb size={16} style={{ marginRight: "8px" }} />
|
||||
<Lightbulb size={16} className="icon-mr-8" />
|
||||
Start Planning
|
||||
</button>
|
||||
</div>
|
||||
@@ -829,7 +821,7 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
||||
|
||||
{view.type === "loading" && (
|
||||
<div className="planning-loading">
|
||||
<Loader2 size={40} className="spin" style={{ color: "var(--todo)" }} />
|
||||
<Loader2 size={40} className="spin icon-todo" />
|
||||
<p>{streamingOutput ? "AI is thinking..." : "Generating next question..."}</p>
|
||||
<div className="planning-thinking-container">
|
||||
<button
|
||||
@@ -861,21 +853,13 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
||||
<div
|
||||
className="ai-error-panel"
|
||||
role="alert"
|
||||
style={{
|
||||
border: "1px solid var(--color-error, #dc2626)",
|
||||
borderRadius: "10px",
|
||||
background: "color-mix(in srgb, var(--color-error, #dc2626) 10%, transparent)",
|
||||
padding: "14px",
|
||||
display: "grid",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
<div className="ai-error-icon" style={{ fontSize: "20px" }}>⚠️</div>
|
||||
<div className="ai-error-icon">⚠️</div>
|
||||
<div className="ai-error-message">{view.errorMessage}</div>
|
||||
<div className="ai-error-actions" style={{ display: "flex", gap: "8px" }}>
|
||||
<div className="ai-error-actions">
|
||||
<button className="btn btn-primary" onClick={() => void handleRetryFromError()} disabled={isRetrying}>
|
||||
{isRetrying ? <Loader2 size={14} className="spin" /> : <RefreshCw size={14} />}
|
||||
<span style={{ marginLeft: "6px" }}>{isRetrying ? "Retrying..." : "Retry"}</span>
|
||||
<span className="icon-ml-6">{isRetrying ? "Retrying..." : "Retry"}</span>
|
||||
</button>
|
||||
<button className="btn" onClick={handleCancel} disabled={isRetrying}>Dismiss</button>
|
||||
</div>
|
||||
@@ -886,7 +870,7 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
||||
|
||||
{view.type === "creating" && (
|
||||
<div className="planning-loading">
|
||||
<Loader2 size={40} className="spin" style={{ color: "var(--todo)" }} />
|
||||
<Loader2 size={40} className="spin icon-todo" />
|
||||
<p>Creating tasks...</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -1139,7 +1123,7 @@ function QuestionForm({ question, progress, historyEntries, onSubmit, onBack }:
|
||||
<div className="planning-actions">
|
||||
{onBack && (
|
||||
<button className="btn" onClick={onBack}>
|
||||
<ArrowLeft size={16} style={{ marginRight: "4px" }} />
|
||||
<ArrowLeft size={16} className="icon-mr-4" />
|
||||
Back
|
||||
</button>
|
||||
)}
|
||||
@@ -1149,7 +1133,7 @@ function QuestionForm({ question, progress, historyEntries, onSubmit, onBack }:
|
||||
disabled={!isValid()}
|
||||
>
|
||||
Continue
|
||||
<ArrowRight size={16} style={{ marginLeft: "4px" }} />
|
||||
<ArrowRight size={16} className="icon-ml-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1201,7 +1185,7 @@ function SummaryView({
|
||||
)}
|
||||
|
||||
<div className="planning-summary-header">
|
||||
<CheckCircle size={24} style={{ color: "var(--color-success)" }} />
|
||||
<CheckCircle size={24} className="icon-success" />
|
||||
<h4>Planning Complete!</h4>
|
||||
<p className="text-muted">Review and refine your task before creating it.</p>
|
||||
</div>
|
||||
@@ -1282,7 +1266,7 @@ function SummaryView({
|
||||
|
||||
<div className="planning-actions planning-summary-actions">
|
||||
<button className="btn" onClick={onRefine} disabled={isLoading}>
|
||||
<ArrowLeft size={16} style={{ marginRight: "4px" }} />
|
||||
<ArrowLeft size={16} className="icon-mr-4" />
|
||||
Refine Further
|
||||
</button>
|
||||
<div className="planning-summary-actions-right">
|
||||
@@ -1294,12 +1278,12 @@ function SummaryView({
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2 size={16} className="spin" style={{ marginRight: "8px" }} />
|
||||
<Loader2 size={16} className="spin icon-mr-8" />
|
||||
Breaking down...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ListTree size={16} style={{ marginRight: "8px" }} />
|
||||
<ListTree size={16} className="icon-mr-8" />
|
||||
Break into Tasks
|
||||
</>
|
||||
)}
|
||||
@@ -1311,12 +1295,12 @@ function SummaryView({
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2 size={16} className="spin" style={{ marginRight: "8px" }} />
|
||||
<Loader2 size={16} className="spin icon-mr-8" />
|
||||
Creating...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<CheckCircle size={16} style={{ marginRight: "8px" }} />
|
||||
<CheckCircle size={16} className="icon-mr-8" />
|
||||
Create Task
|
||||
</>
|
||||
)}
|
||||
@@ -1480,7 +1464,7 @@ function BreakdownView({
|
||||
<div className="planning-summary">
|
||||
<div className="planning-view-scroll planning-summary-scroll">
|
||||
<div className="planning-summary-header">
|
||||
<ListTree size={24} style={{ color: "var(--triage)" }} />
|
||||
<ListTree size={24} className="icon-triage" />
|
||||
<h4>Break into Tasks</h4>
|
||||
<p className="text-muted">
|
||||
Review and edit the subtasks generated from your plan. Adjust titles,
|
||||
@@ -1516,8 +1500,7 @@ function BreakdownView({
|
||||
onDragLeave={handleDragLeave}
|
||||
>
|
||||
<div
|
||||
className="detail-title-row subtask-item-header"
|
||||
style={{ justifyContent: "space-between" }}
|
||||
className="detail-title-row subtask-item-header subtask-item-header--between"
|
||||
>
|
||||
<div className="subtask-drag-handle" title="Drag to reorder">
|
||||
<GripVertical size={16} />
|
||||
@@ -1652,7 +1635,7 @@ function BreakdownView({
|
||||
})}
|
||||
|
||||
<button type="button" className="btn" onClick={addSubtask} disabled={isLoading}>
|
||||
<Plus size={16} style={{ marginRight: 6 }} /> Add subtask
|
||||
<Plus size={16} className="icon-mr-6" /> Add subtask
|
||||
</button>
|
||||
|
||||
{hasDependencyCycle(subtasks) && (
|
||||
@@ -1665,7 +1648,7 @@ function BreakdownView({
|
||||
|
||||
<div className="planning-actions planning-summary-actions">
|
||||
<button className="btn" onClick={onBack} disabled={isLoading}>
|
||||
<ArrowLeft size={16} style={{ marginRight: "4px" }} />
|
||||
<ArrowLeft size={16} className="icon-mr-4" />
|
||||
Back to Summary
|
||||
</button>
|
||||
<button
|
||||
@@ -1675,7 +1658,7 @@ function BreakdownView({
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<Loader2 size={16} className="spin" style={{ marginRight: 8 }} />
|
||||
<Loader2 size={16} className="spin icon-mr-6" />
|
||||
Creating...
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -519,7 +519,7 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
|
||||
<div className="modal modal-lg planning-modal">
|
||||
<div className="modal-header">
|
||||
<div className="detail-title-row">
|
||||
<ListTree size={20} style={{ color: "var(--triage)" }} />
|
||||
<ListTree size={20} className="icon-triage" />
|
||||
<h3>Subtask Breakdown</h3>
|
||||
</div>
|
||||
<div className="modal-header-actions">
|
||||
@@ -565,7 +565,7 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
|
||||
<div className="conversation-separator" />
|
||||
</>
|
||||
)}
|
||||
<Loader2 size={40} className="spin" style={{ color: "var(--todo)" }} />
|
||||
<Loader2 size={40} className="spin icon-todo" />
|
||||
<p>AI is generating subtasks...</p>
|
||||
<div className="planning-thinking-container">
|
||||
<button className="planning-thinking-toggle" onClick={() => setShowThinking(!showThinking)} type="button">
|
||||
@@ -592,21 +592,13 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
|
||||
<div
|
||||
className="ai-error-panel"
|
||||
role="alert"
|
||||
style={{
|
||||
border: "1px solid var(--color-error, #dc2626)",
|
||||
borderRadius: "10px",
|
||||
background: "color-mix(in srgb, var(--color-error, #dc2626) 10%, transparent)",
|
||||
padding: "14px",
|
||||
display: "grid",
|
||||
gap: "10px",
|
||||
}}
|
||||
>
|
||||
<div className="ai-error-icon" style={{ fontSize: "20px" }}>⚠️</div>
|
||||
<div className="ai-error-icon">⚠️</div>
|
||||
<div className="ai-error-message">{view.errorMessage}</div>
|
||||
<div className="ai-error-actions" style={{ display: "flex", gap: "8px" }}>
|
||||
<div className="ai-error-actions">
|
||||
<button className="btn btn-primary" onClick={() => void handleRetry()} disabled={isRetrying}>
|
||||
{isRetrying ? <Loader2 size={14} className="spin" /> : <RefreshCw size={14} />}
|
||||
<span style={{ marginLeft: "6px" }}>{isRetrying ? "Retrying..." : "Retry"}</span>
|
||||
<span className="icon-ml-6">{isRetrying ? "Retrying..." : "Retry"}</span>
|
||||
</button>
|
||||
<button className="btn" onClick={() => void handleClose()} disabled={isRetrying}>Cancel</button>
|
||||
</div>
|
||||
@@ -626,7 +618,7 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
|
||||
)}
|
||||
|
||||
<div className="planning-summary-header">
|
||||
<CheckCircle size={24} style={{ color: "var(--color-success)" }} />
|
||||
<CheckCircle size={24} className="icon-success" />
|
||||
<h4>Review your subtasks</h4>
|
||||
<p className="text-muted">Edit titles, descriptions, sizes, and dependencies before creating all tasks at once.</p>
|
||||
</div>
|
||||
@@ -656,7 +648,7 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
|
||||
onDrop={handleDrop(subtask.id)}
|
||||
onDragLeave={handleDragLeave}
|
||||
>
|
||||
<div className="detail-title-row subtask-item-header" style={{ justifyContent: "space-between" }}>
|
||||
<div className="detail-title-row subtask-item-header subtask-item-header--between">
|
||||
<div className="subtask-drag-handle" title="Drag to reorder">
|
||||
<GripVertical size={16} />
|
||||
<strong>{subtask.id}</strong>
|
||||
@@ -765,7 +757,7 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
|
||||
})}
|
||||
|
||||
<button type="button" className="btn" onClick={addSubtask} disabled={view.type === "creating"}>
|
||||
<Plus size={16} style={{ marginRight: 6 }} /> Add subtask
|
||||
<Plus size={16} className="icon-mr-6" /> Add subtask
|
||||
</button>
|
||||
|
||||
{hasDependencyCycle(subtasks) && (
|
||||
@@ -781,7 +773,7 @@ export function SubtaskBreakdownModal({ isOpen, onClose, initialDescription, onT
|
||||
<button className="btn btn-primary" onClick={() => void handleCreateTasks()} disabled={view.type === "creating" || isInvalid}>
|
||||
{view.type === "creating" ? (
|
||||
<>
|
||||
<Loader2 size={16} className="spin" style={{ marginRight: 8 }} />
|
||||
<Loader2 size={16} className="spin icon-mr-6" />
|
||||
Creating...
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -16004,6 +16004,229 @@ body[data-color-theme="terminal"][data-theme="light"]::before {
|
||||
}
|
||||
}
|
||||
|
||||
/* === Planning & Breakdown Modal — Inline Style Replacements === */
|
||||
|
||||
/* Icon color utilities */
|
||||
.icon-triage {
|
||||
color: var(--triage);
|
||||
}
|
||||
.icon-triage-lg {
|
||||
color: var(--triage);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.icon-todo {
|
||||
color: var(--todo);
|
||||
}
|
||||
.icon-success {
|
||||
color: var(--color-success);
|
||||
}
|
||||
.icon-text-secondary {
|
||||
color: var(--text-secondary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.icon-milestone {
|
||||
color: var(--icon-milestone);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.icon-slice {
|
||||
color: var(--icon-slice);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.icon-feature {
|
||||
color: var(--icon-feature);
|
||||
margin-top: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Icon margin utilities */
|
||||
.icon-mr-4 {
|
||||
margin-right: 4px;
|
||||
}
|
||||
.icon-mr-6 {
|
||||
margin-right: 6px;
|
||||
}
|
||||
.icon-mr-8 {
|
||||
margin-right: 8px;
|
||||
}
|
||||
.icon-ml-4 {
|
||||
margin-left: 4px;
|
||||
}
|
||||
.icon-ml-6 {
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
/* Error panel */
|
||||
.ai-error-panel {
|
||||
border: 1px solid var(--color-error);
|
||||
border-radius: 10px;
|
||||
background: color-mix(in srgb, var(--color-error) 10%, transparent);
|
||||
padding: 14px;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
.ai-error-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
.ai-error-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* Text link button */
|
||||
.text-link-btn {
|
||||
font-size: 12px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Form hint error variant */
|
||||
.form-hint-error {
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
/* Text muted small */
|
||||
.text-muted-sm {
|
||||
margin-left: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Model selector spacing */
|
||||
.model-select-row--left {
|
||||
margin-right: auto;
|
||||
min-width: min(100%, 320px);
|
||||
max-width: 420px;
|
||||
text-align: left;
|
||||
}
|
||||
.model-selector-current--spaced {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* Roadmap hierarchy (MissionInterviewModal) */
|
||||
.roadmap-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.roadmap-card {
|
||||
border: 1px solid var(--border-primary);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.roadmap-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
background: var(--bg-secondary);
|
||||
cursor: pointer;
|
||||
}
|
||||
.roadmap-card-body {
|
||||
padding: 0 12px 12px 36px;
|
||||
}
|
||||
.roadmap-textarea-sm {
|
||||
font-size: 12px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.roadmap-textarea-md {
|
||||
font-size: 12px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.roadmap-textarea-xs {
|
||||
font-size: 11px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.roadmap-field-label {
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
.roadmap-field-group {
|
||||
margin-top: 6px;
|
||||
}
|
||||
.roadmap-slice-card {
|
||||
margin-top: 8px;
|
||||
border: 1px solid var(--border-primary);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.roadmap-slice-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 10px;
|
||||
background: var(--bg-tertiary);
|
||||
cursor: pointer;
|
||||
}
|
||||
.roadmap-slice-body {
|
||||
padding: 8px 10px 10px 30px;
|
||||
}
|
||||
.roadmap-slice-field-group {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.roadmap-input-title {
|
||||
flex: 1;
|
||||
padding: 4px 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.roadmap-input-subtitle {
|
||||
flex: 1;
|
||||
padding: 3px 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.roadmap-input-feature {
|
||||
width: 100%;
|
||||
padding: 2px 6px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.roadmap-feature-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
.roadmap-feature-row + .roadmap-feature-row {
|
||||
border-top: 1px solid var(--border-primary);
|
||||
}
|
||||
.roadmap-feature-text {
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
margin: 2px 0 0 6px;
|
||||
}
|
||||
.roadmap-feature-text--italic {
|
||||
font-size: 11px;
|
||||
color: var(--text-secondary);
|
||||
margin: 2px 0 0 6px;
|
||||
font-style: italic;
|
||||
}
|
||||
.roadmap-feature-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.roadmap-add-feature-btn {
|
||||
font-size: 11px;
|
||||
padding: 4px 8px;
|
||||
margin-top: 6px;
|
||||
gap: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.roadmap-shrink {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Subtask header override */
|
||||
.subtask-item-header--between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
USAGE INDICATOR STYLES
|
||||
============================================================ */
|
||||
|
||||
Reference in New Issue
Block a user