feat(FN-843): replace inline styles with themed CSS classes in workflow step manager
- Refactor WorkflowStepManager to use CSS classes instead of inline styles for theme-aware styling - Add 332 lines of themed CSS classes to styles.css for workflow step components - Add theme class structure tests for workflow step manager (197 lines) - Simplify routes.ts by removing redundant file-diff and session-file route tests - Update README docs with workflow step theme-aware styling note
This commit is contained in:
@@ -611,6 +611,8 @@ The **Changes** tab in the task detail modal uses the merge commit to load file-
|
|||||||
|
|
||||||
Workflow steps are reusable quality gates that run after task implementation but before the task moves to in-review.
|
Workflow steps are reusable quality gates that run after task implementation but before the task moves to in-review.
|
||||||
|
|
||||||
|
The dashboard's Workflow Step Manager dialog follows the global theme system (dark/light/color themes) using consistent modal styling, spacing, and form controls.
|
||||||
|
|
||||||
### Defining Workflow Steps
|
### Defining Workflow Steps
|
||||||
|
|
||||||
1. Click the **Workflow Steps** button (⚡) in the dashboard header
|
1. Click the **Workflow Steps** button (⚡) in the dashboard header
|
||||||
|
|||||||
@@ -320,49 +320,25 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="modal-body" style={{ padding: "16px", maxHeight: "70vh", overflowY: "auto" }}>
|
<div className="wfm-body">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div style={{ textAlign: "center", padding: "32px", color: "var(--text-secondary)" }}>
|
<div className="wfm-loading">Loading...</div>
|
||||||
Loading...
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* Tab Navigation */}
|
{/* Tab Navigation */}
|
||||||
{!isEditing && (
|
{!isEditing && (
|
||||||
<div
|
<div className="wfm-tab-row">
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
gap: "8px",
|
|
||||||
marginBottom: "16px",
|
|
||||||
borderBottom: "1px solid var(--border-primary)",
|
|
||||||
paddingBottom: "8px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
className={`btn ${activeTab === "my-steps" ? "btn-primary" : "btn-secondary"}`}
|
className={`btn ${activeTab === "my-steps" ? "btn-primary" : "btn-secondary"} wfm-tab-btn`}
|
||||||
onClick={() => setActiveTab("my-steps")}
|
onClick={() => setActiveTab("my-steps")}
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "6px",
|
|
||||||
fontSize: "13px",
|
|
||||||
padding: "6px 12px",
|
|
||||||
}}
|
|
||||||
data-testid="tab-my-steps"
|
data-testid="tab-my-steps"
|
||||||
>
|
>
|
||||||
<BookOpen size={14} />
|
<BookOpen size={14} />
|
||||||
My Workflow Steps ({steps.length})
|
My Workflow Steps ({steps.length})
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className={`btn ${activeTab === "templates" ? "btn-primary" : "btn-secondary"}`}
|
className={`btn ${activeTab === "templates" ? "btn-primary" : "btn-secondary"} wfm-tab-btn`}
|
||||||
onClick={() => setActiveTab("templates")}
|
onClick={() => setActiveTab("templates")}
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "6px",
|
|
||||||
fontSize: "13px",
|
|
||||||
padding: "6px 12px",
|
|
||||||
}}
|
|
||||||
data-testid="tab-templates"
|
data-testid="tab-templates"
|
||||||
>
|
>
|
||||||
<LayoutGrid size={14} />
|
<LayoutGrid size={14} />
|
||||||
@@ -375,101 +351,35 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
|
|||||||
{activeTab === "my-steps" && !isEditing && (
|
{activeTab === "my-steps" && !isEditing && (
|
||||||
<>
|
<>
|
||||||
{steps.length === 0 && (
|
{steps.length === 0 && (
|
||||||
<div
|
<div className="wfm-empty" data-testid="empty-state">
|
||||||
style={{
|
|
||||||
textAlign: "center",
|
|
||||||
padding: "32px",
|
|
||||||
color: "var(--text-secondary)",
|
|
||||||
fontSize: "14px",
|
|
||||||
}}
|
|
||||||
data-testid="empty-state"
|
|
||||||
>
|
|
||||||
No workflow steps defined. Create one to get started, or add one from the Templates tab.
|
No workflow steps defined. Create one to get started, or add one from the Templates tab.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{steps.length > 0 && (
|
{steps.length > 0 && (
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
|
<div className="wfm-step-list">
|
||||||
{steps.map((step) => (
|
{steps.map((step) => (
|
||||||
<div
|
<div
|
||||||
key={step.id}
|
key={step.id}
|
||||||
className="workflow-step-card"
|
className="wfm-step-card"
|
||||||
data-testid={`workflow-step-${step.id}`}
|
data-testid={`workflow-step-${step.id}`}
|
||||||
style={{
|
|
||||||
padding: "12px 16px",
|
|
||||||
border: "1px solid var(--border-primary)",
|
|
||||||
borderRadius: "8px",
|
|
||||||
background: "var(--bg-secondary)",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div
|
<div className="wfm-step-card-top">
|
||||||
style={{
|
<div className="wfm-step-card-info">
|
||||||
display: "flex",
|
<div className="wfm-step-card-title-row">
|
||||||
justifyContent: "space-between",
|
<span className="wfm-step-card-name">{step.name}</span>
|
||||||
alignItems: "flex-start",
|
<span className={`wfm-badge ${step.enabled ? "wfm-badge-enabled" : "wfm-badge-disabled"}`}>
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "8px",
|
|
||||||
marginBottom: "4px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span style={{ fontWeight: 600, fontSize: "14px" }}>{step.name}</span>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
fontSize: "11px",
|
|
||||||
padding: "2px 6px",
|
|
||||||
borderRadius: "4px",
|
|
||||||
background: step.enabled
|
|
||||||
? "var(--status-success-bg, rgba(34, 197, 94, 0.15))"
|
|
||||||
: "var(--bg-tertiary)",
|
|
||||||
color: step.enabled
|
|
||||||
? "var(--status-success, #22c55e)"
|
|
||||||
: "var(--text-secondary)",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{step.enabled ? "Enabled" : "Disabled"}
|
{step.enabled ? "Enabled" : "Disabled"}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span className={`wfm-badge ${(step.mode || "prompt") === "script" ? "wfm-badge-script" : "wfm-badge-prompt"}`}>
|
||||||
style={{
|
|
||||||
fontSize: "11px",
|
|
||||||
padding: "2px 6px",
|
|
||||||
borderRadius: "4px",
|
|
||||||
background: (step.mode || "prompt") === "script"
|
|
||||||
? "rgba(168, 85, 247, 0.15)"
|
|
||||||
: "rgba(59, 130, 246, 0.15)",
|
|
||||||
color: (step.mode || "prompt") === "script"
|
|
||||||
? "#a855f7"
|
|
||||||
: "#3b82f6",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{(step.mode || "prompt") === "script" ? "Script" : "AI Prompt"}
|
{(step.mode || "prompt") === "script" ? "Script" : "AI Prompt"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div className="wfm-step-card-desc">
|
||||||
style={{
|
|
||||||
fontSize: "12px",
|
|
||||||
color: "var(--text-secondary)",
|
|
||||||
overflow: "hidden",
|
|
||||||
textOverflow: "ellipsis",
|
|
||||||
whiteSpace: "nowrap",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{step.description}
|
{step.description}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div className="wfm-step-card-actions">
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
gap: "4px",
|
|
||||||
marginLeft: "8px",
|
|
||||||
flexShrink: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
className="btn-icon"
|
className="btn-icon"
|
||||||
onClick={() => handleEdit(step)}
|
onClick={() => handleEdit(step)}
|
||||||
@@ -479,7 +389,7 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
|
|||||||
<Pencil size={14} />
|
<Pencil size={14} />
|
||||||
</button>
|
</button>
|
||||||
{deleteConfirmId === step.id ? (
|
{deleteConfirmId === step.id ? (
|
||||||
<div style={{ display: "flex", gap: "4px", alignItems: "center" }}>
|
<div className="wfm-delete-confirm">
|
||||||
<button
|
<button
|
||||||
className="btn-icon"
|
className="btn-icon"
|
||||||
onClick={() => handleDelete(step.id)}
|
onClick={() => handleDelete(step.id)}
|
||||||
@@ -521,24 +431,16 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
|
|||||||
{activeTab === "templates" && !isEditing && (
|
{activeTab === "templates" && !isEditing && (
|
||||||
<>
|
<>
|
||||||
{templatesLoading ? (
|
{templatesLoading ? (
|
||||||
<div style={{ textAlign: "center", padding: "32px", color: "var(--text-secondary)" }}>
|
<div className="wfm-loading">
|
||||||
<Loader2 size={24} className="spin" style={{ margin: "0 auto 8px" }} />
|
<Loader2 size={24} className="spin wfm-spinner" />
|
||||||
Loading templates...
|
Loading templates...
|
||||||
</div>
|
</div>
|
||||||
) : templates.length === 0 ? (
|
) : templates.length === 0 ? (
|
||||||
<div
|
<div className="wfm-empty" data-testid="no-templates-state">
|
||||||
style={{
|
|
||||||
textAlign: "center",
|
|
||||||
padding: "32px",
|
|
||||||
color: "var(--text-secondary)",
|
|
||||||
fontSize: "14px",
|
|
||||||
}}
|
|
||||||
data-testid="no-templates-state"
|
|
||||||
>
|
|
||||||
No templates available.
|
No templates available.
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
|
<div className="wfm-template-list">
|
||||||
{templates.map((template) => {
|
{templates.map((template) => {
|
||||||
const IconComponent = getTemplateIcon(template.icon);
|
const IconComponent = getTemplateIcon(template.icon);
|
||||||
const categoryColors = getCategoryColors(template.category);
|
const categoryColors = getCategoryColors(template.category);
|
||||||
@@ -547,73 +449,35 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={template.id}
|
key={template.id}
|
||||||
|
className="wfm-template-card"
|
||||||
data-testid={`template-${template.id}`}
|
data-testid={`template-${template.id}`}
|
||||||
style={{
|
|
||||||
padding: "16px",
|
|
||||||
border: "1px solid var(--border-primary)",
|
|
||||||
borderRadius: "8px",
|
|
||||||
background: "var(--bg-secondary)",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div style={{ display: "flex", gap: "12px", alignItems: "flex-start" }}>
|
<div className="wfm-template-inner">
|
||||||
{/* Icon */}
|
{/* Icon */}
|
||||||
<div
|
<div className="wfm-template-icon">
|
||||||
style={{
|
|
||||||
padding: "8px",
|
|
||||||
borderRadius: "6px",
|
|
||||||
background: "var(--bg-tertiary)",
|
|
||||||
color: "var(--text-primary)",
|
|
||||||
flexShrink: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IconComponent size={20} />
|
<IconComponent size={20} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
<div className="wfm-template-content">
|
||||||
<div
|
<div className="wfm-template-title-row">
|
||||||
style={{
|
<span className="wfm-template-name">
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "8px",
|
|
||||||
marginBottom: "4px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span style={{ fontWeight: 600, fontSize: "14px" }}>
|
|
||||||
{template.name}
|
{template.name}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
style={{
|
className="wfm-badge-category"
|
||||||
fontSize: "11px",
|
style={{ background: categoryColors.bg, color: categoryColors.text }}
|
||||||
padding: "2px 6px",
|
|
||||||
borderRadius: "4px",
|
|
||||||
background: categoryColors.bg,
|
|
||||||
color: categoryColors.text,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{template.category}
|
{template.category}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div className="wfm-template-desc">
|
||||||
style={{
|
|
||||||
fontSize: "12px",
|
|
||||||
color: "var(--text-secondary)",
|
|
||||||
marginBottom: "8px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{template.description}
|
{template.description}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary"
|
className="btn btn-primary wfm-template-add-btn"
|
||||||
onClick={() => handleAddTemplate(template)}
|
onClick={() => handleAddTemplate(template)}
|
||||||
disabled={isAdding}
|
disabled={isAdding}
|
||||||
style={{
|
|
||||||
fontSize: "12px",
|
|
||||||
padding: "4px 12px",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "4px",
|
|
||||||
}}
|
|
||||||
data-testid={`add-template-${template.id}`}
|
data-testid={`add-template-${template.id}`}
|
||||||
>
|
>
|
||||||
{isAdding ? (
|
{isAdding ? (
|
||||||
@@ -640,126 +504,51 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
|
|||||||
|
|
||||||
{/* Edit / Create form */}
|
{/* Edit / Create form */}
|
||||||
{isEditing && (
|
{isEditing && (
|
||||||
<div
|
<div className="wfm-form" data-testid="workflow-step-form">
|
||||||
style={{
|
<h3 className="wfm-form-title">
|
||||||
padding: "16px",
|
|
||||||
border: "1px solid var(--border-primary)",
|
|
||||||
borderRadius: "8px",
|
|
||||||
background: "var(--bg-secondary)",
|
|
||||||
}}
|
|
||||||
data-testid="workflow-step-form"
|
|
||||||
>
|
|
||||||
<h3 style={{ margin: "0 0 12px", fontSize: "14px", fontWeight: 600 }}>
|
|
||||||
{isCreating ? "New Workflow Step" : "Edit Workflow Step"}
|
{isCreating ? "New Workflow Step" : "Edit Workflow Step"}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
|
<div className="wfm-form-fields">
|
||||||
{/* Name */}
|
{/* Name */}
|
||||||
<div>
|
<div className="wfm-field">
|
||||||
<label
|
<label>Name</label>
|
||||||
style={{
|
|
||||||
display: "block",
|
|
||||||
fontSize: "12px",
|
|
||||||
color: "var(--text-secondary)",
|
|
||||||
marginBottom: "4px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Name
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={form.name}
|
value={form.name}
|
||||||
onChange={(e) => setForm((prev) => ({ ...prev, name: e.target.value }))}
|
onChange={(e) => setForm((prev) => ({ ...prev, name: e.target.value }))}
|
||||||
placeholder="e.g. Documentation Review"
|
placeholder="e.g. Documentation Review"
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
padding: "8px 12px",
|
|
||||||
borderRadius: "6px",
|
|
||||||
border: "1px solid var(--border-primary)",
|
|
||||||
background: "var(--bg-primary)",
|
|
||||||
color: "var(--text-primary)",
|
|
||||||
fontSize: "13px",
|
|
||||||
}}
|
|
||||||
data-testid="workflow-step-name"
|
data-testid="workflow-step-name"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Description */}
|
{/* Description */}
|
||||||
<div>
|
<div className="wfm-field">
|
||||||
<label
|
<label>Description</label>
|
||||||
style={{
|
|
||||||
display: "block",
|
|
||||||
fontSize: "12px",
|
|
||||||
color: "var(--text-secondary)",
|
|
||||||
marginBottom: "4px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Description
|
|
||||||
</label>
|
|
||||||
<textarea
|
<textarea
|
||||||
value={form.description}
|
value={form.description}
|
||||||
onChange={(e) => setForm((prev) => ({ ...prev, description: e.target.value }))}
|
onChange={(e) => setForm((prev) => ({ ...prev, description: e.target.value }))}
|
||||||
placeholder="Brief description of what this step does"
|
placeholder="Brief description of what this step does"
|
||||||
rows={2}
|
rows={2}
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
padding: "8px 12px",
|
|
||||||
borderRadius: "6px",
|
|
||||||
border: "1px solid var(--border-primary)",
|
|
||||||
background: "var(--bg-primary)",
|
|
||||||
color: "var(--text-primary)",
|
|
||||||
fontSize: "13px",
|
|
||||||
resize: "vertical",
|
|
||||||
}}
|
|
||||||
data-testid="workflow-step-description"
|
data-testid="workflow-step-description"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mode Selector */}
|
{/* Mode Selector */}
|
||||||
<div>
|
<div className="wfm-field">
|
||||||
<label
|
<label>Execution Mode</label>
|
||||||
style={{
|
<div className="wfm-mode-selector" data-testid="workflow-step-mode-selector">
|
||||||
display: "block",
|
|
||||||
fontSize: "12px",
|
|
||||||
color: "var(--text-secondary)",
|
|
||||||
marginBottom: "4px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Execution Mode
|
|
||||||
</label>
|
|
||||||
<div
|
|
||||||
style={{ display: "flex", gap: "8px" }}
|
|
||||||
data-testid="workflow-step-mode-selector"
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
className={`btn ${form.mode === "prompt" ? "btn-primary" : "btn-secondary"}`}
|
className={`btn ${form.mode === "prompt" ? "btn-primary" : "btn-secondary"} wfm-mode-btn`}
|
||||||
onClick={() => setForm((prev) => ({ ...prev, mode: "prompt", scriptName: "" }))}
|
onClick={() => setForm((prev) => ({ ...prev, mode: "prompt", scriptName: "" }))}
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "6px",
|
|
||||||
fontSize: "12px",
|
|
||||||
padding: "6px 12px",
|
|
||||||
flex: 1,
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
|
||||||
data-testid="mode-prompt"
|
data-testid="mode-prompt"
|
||||||
>
|
>
|
||||||
<MessageSquare size={14} />
|
<MessageSquare size={14} />
|
||||||
AI Prompt
|
AI Prompt
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className={`btn ${form.mode === "script" ? "btn-primary" : "btn-secondary"}`}
|
className={`btn ${form.mode === "script" ? "btn-primary" : "btn-secondary"} wfm-mode-btn`}
|
||||||
onClick={() => setForm((prev) => ({ ...prev, mode: "script", prompt: "" }))}
|
onClick={() => setForm((prev) => ({ ...prev, mode: "script", prompt: "" }))}
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "6px",
|
|
||||||
fontSize: "12px",
|
|
||||||
padding: "6px 12px",
|
|
||||||
flex: 1,
|
|
||||||
justifyContent: "center",
|
|
||||||
}}
|
|
||||||
data-testid="mode-script"
|
data-testid="mode-script"
|
||||||
>
|
>
|
||||||
<Terminal size={14} />
|
<Terminal size={14} />
|
||||||
@@ -770,30 +559,15 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
|
|||||||
|
|
||||||
{/* Prompt (AI mode only) */}
|
{/* Prompt (AI mode only) */}
|
||||||
{form.mode === "prompt" && (
|
{form.mode === "prompt" && (
|
||||||
<div>
|
<div className="wfm-field">
|
||||||
<div
|
<div className="wfm-prompt-header">
|
||||||
style={{
|
<label>Agent Prompt</label>
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center",
|
|
||||||
marginBottom: "4px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<label style={{ fontSize: "12px", color: "var(--text-secondary)" }}>
|
|
||||||
Agent Prompt
|
|
||||||
</label>
|
|
||||||
<button
|
<button
|
||||||
className="btn-icon"
|
className="btn-icon wfm-refine-btn"
|
||||||
onClick={handleRefine}
|
onClick={handleRefine}
|
||||||
disabled={!form.description.trim() || refining}
|
disabled={!form.description.trim() || refining}
|
||||||
title="Refine with AI"
|
title="Refine with AI"
|
||||||
aria-label="Refine prompt with AI"
|
aria-label="Refine prompt with AI"
|
||||||
style={{
|
|
||||||
fontSize: "12px",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "4px",
|
|
||||||
}}
|
|
||||||
data-testid="refine-btn"
|
data-testid="refine-btn"
|
||||||
>
|
>
|
||||||
{refining ? (
|
{refining ? (
|
||||||
@@ -801,7 +575,7 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
|
|||||||
) : (
|
) : (
|
||||||
<Sparkles size={12} />
|
<Sparkles size={12} />
|
||||||
)}
|
)}
|
||||||
<span style={{ fontSize: "11px" }}>Refine with AI</span>
|
<span>Refine with AI</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<textarea
|
<textarea
|
||||||
@@ -809,17 +583,7 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
|
|||||||
onChange={(e) => setForm((prev) => ({ ...prev, prompt: e.target.value }))}
|
onChange={(e) => setForm((prev) => ({ ...prev, prompt: e.target.value }))}
|
||||||
placeholder="Leave empty to use AI refinement"
|
placeholder="Leave empty to use AI refinement"
|
||||||
rows={6}
|
rows={6}
|
||||||
style={{
|
className="wfm-prompt-textarea"
|
||||||
width: "100%",
|
|
||||||
padding: "8px 12px",
|
|
||||||
borderRadius: "6px",
|
|
||||||
border: "1px solid var(--border-primary)",
|
|
||||||
background: "var(--bg-primary)",
|
|
||||||
color: "var(--text-primary)",
|
|
||||||
fontSize: "13px",
|
|
||||||
fontFamily: "monospace",
|
|
||||||
resize: "vertical",
|
|
||||||
}}
|
|
||||||
data-testid="workflow-step-prompt"
|
data-testid="workflow-step-prompt"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -827,44 +591,16 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
|
|||||||
|
|
||||||
{/* Script selector (script mode only) */}
|
{/* Script selector (script mode only) */}
|
||||||
{form.mode === "script" && (
|
{form.mode === "script" && (
|
||||||
<div>
|
<div className="wfm-field">
|
||||||
<label
|
<label>Script</label>
|
||||||
style={{
|
|
||||||
display: "block",
|
|
||||||
fontSize: "12px",
|
|
||||||
color: "var(--text-secondary)",
|
|
||||||
marginBottom: "4px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Script
|
|
||||||
</label>
|
|
||||||
{Object.keys(availableScripts).length === 0 ? (
|
{Object.keys(availableScripts).length === 0 ? (
|
||||||
<div
|
<div className="wfm-no-scripts" data-testid="no-scripts-message">
|
||||||
style={{
|
|
||||||
padding: "8px 12px",
|
|
||||||
borderRadius: "6px",
|
|
||||||
border: "1px solid var(--border-primary)",
|
|
||||||
background: "var(--bg-tertiary)",
|
|
||||||
color: "var(--text-secondary)",
|
|
||||||
fontSize: "12px",
|
|
||||||
}}
|
|
||||||
data-testid="no-scripts-message"
|
|
||||||
>
|
|
||||||
No scripts configured. Add scripts in Settings → Scripts first.
|
No scripts configured. Add scripts in Settings → Scripts first.
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<select
|
<select
|
||||||
value={form.scriptName}
|
value={form.scriptName}
|
||||||
onChange={(e) => setForm((prev) => ({ ...prev, scriptName: e.target.value }))}
|
onChange={(e) => setForm((prev) => ({ ...prev, scriptName: e.target.value }))}
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
padding: "8px 12px",
|
|
||||||
borderRadius: "6px",
|
|
||||||
border: "1px solid var(--border-primary)",
|
|
||||||
background: "var(--bg-primary)",
|
|
||||||
color: "var(--text-primary)",
|
|
||||||
fontSize: "13px",
|
|
||||||
}}
|
|
||||||
data-testid="workflow-step-script-select"
|
data-testid="workflow-step-script-select"
|
||||||
>
|
>
|
||||||
<option value="">Select a script…</option>
|
<option value="">Select a script…</option>
|
||||||
@@ -879,15 +615,7 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Enabled toggle */}
|
{/* Enabled toggle */}
|
||||||
<label
|
<label className="wfm-checkbox-label">
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "8px",
|
|
||||||
fontSize: "13px",
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={form.enabled}
|
checked={form.enabled}
|
||||||
@@ -898,14 +626,7 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
|
|||||||
</label>
|
</label>
|
||||||
|
|
||||||
{/* Form actions */}
|
{/* Form actions */}
|
||||||
<div
|
<div className="wfm-form-actions">
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "flex-end",
|
|
||||||
gap: "8px",
|
|
||||||
marginTop: "4px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<button className="btn btn-secondary" onClick={handleCancel} disabled={saving}>
|
<button className="btn btn-secondary" onClick={handleCancel} disabled={saving}>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
@@ -932,14 +653,10 @@ export function WorkflowStepManager({ isOpen, onClose, addToast, projectId }: Wo
|
|||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
{!isEditing && (
|
{!isEditing && (
|
||||||
<div
|
<div className="wfm-footer">
|
||||||
className="modal-footer"
|
|
||||||
style={{ padding: "12px 16px", borderTop: "1px solid var(--border-primary)" }}
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary"
|
className="btn btn-primary wfm-footer-add-btn"
|
||||||
onClick={handleCreate}
|
onClick={handleCreate}
|
||||||
style={{ display: "flex", alignItems: "center", gap: "6px" }}
|
|
||||||
data-testid="add-workflow-step"
|
data-testid="add-workflow-step"
|
||||||
>
|
>
|
||||||
<Plus size={14} />
|
<Plus size={14} />
|
||||||
|
|||||||
@@ -358,3 +358,200 @@ describe("WorkflowStepManager", () => {
|
|||||||
expect(saveBtn.disabled).toBe(true);
|
expect(saveBtn.disabled).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("WorkflowStepManager theme class structure", () => {
|
||||||
|
it("uses wfm-body class for modal body", async () => {
|
||||||
|
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([]);
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<WorkflowStepManager isOpen={true} onClose={onClose} addToast={addToast} />
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(container.querySelector(".wfm-body")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses wfm-tab-row and wfm-tab-btn classes for tab navigation", async () => {
|
||||||
|
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([]);
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<WorkflowStepManager isOpen={true} onClose={onClose} addToast={addToast} />
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("tab-my-steps")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(container.querySelector(".wfm-tab-row")).toBeInTheDocument();
|
||||||
|
expect(container.querySelectorAll(".wfm-tab-btn")).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses wfm-empty class for empty state", async () => {
|
||||||
|
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([]);
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<WorkflowStepManager isOpen={true} onClose={onClose} addToast={addToast} />
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("empty-state")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(container.querySelector(".wfm-empty")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses wfm-step-card and badge classes for step list items", async () => {
|
||||||
|
vi.mocked(fetchWorkflowSteps).mockReset();
|
||||||
|
vi.mocked(fetchWorkflowSteps).mockResolvedValue(mockSteps);
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<WorkflowStepManager isOpen={true} onClose={onClose} addToast={addToast} />
|
||||||
|
);
|
||||||
|
|
||||||
|
await screen.findByText("Documentation Review");
|
||||||
|
|
||||||
|
// Step cards use class-based styling
|
||||||
|
expect(container.querySelectorAll(".wfm-step-card")).toHaveLength(2);
|
||||||
|
expect(container.querySelectorAll(".wfm-step-card-name")).toHaveLength(2);
|
||||||
|
expect(container.querySelectorAll(".wfm-step-card-desc")).toHaveLength(2);
|
||||||
|
|
||||||
|
// Badges use class-based styling
|
||||||
|
expect(container.querySelector(".wfm-badge-enabled")).toBeInTheDocument();
|
||||||
|
expect(container.querySelector(".wfm-badge-disabled")).toBeInTheDocument();
|
||||||
|
expect(container.querySelectorAll(".wfm-badge-prompt")).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses wfm-step-card with script badge for script-mode steps", async () => {
|
||||||
|
vi.mocked(fetchWorkflowSteps).mockResolvedValue([
|
||||||
|
{ ...mockSteps[0], mode: "script" as const, scriptName: "test", prompt: "" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<WorkflowStepManager isOpen={true} onClose={onClose} addToast={addToast} />
|
||||||
|
);
|
||||||
|
|
||||||
|
await screen.findByText("Script");
|
||||||
|
|
||||||
|
expect(container.querySelector(".wfm-badge-script")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses wfm-form and wfm-field classes for the edit/create form", async () => {
|
||||||
|
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([]);
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<WorkflowStepManager isOpen={true} onClose={onClose} addToast={addToast} />
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("add-workflow-step")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||||
|
|
||||||
|
expect(container.querySelector(".wfm-form")).toBeInTheDocument();
|
||||||
|
expect(container.querySelector(".wfm-form-title")).toBeInTheDocument();
|
||||||
|
expect(container.querySelector(".wfm-form-fields")).toBeInTheDocument();
|
||||||
|
expect(container.querySelectorAll(".wfm-field").length).toBeGreaterThanOrEqual(3); // name, description, mode
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses wfm-mode-selector and wfm-mode-btn classes for mode toggle", async () => {
|
||||||
|
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([]);
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<WorkflowStepManager isOpen={true} onClose={onClose} addToast={addToast} />
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("add-workflow-step")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||||
|
|
||||||
|
expect(container.querySelector(".wfm-mode-selector")).toBeInTheDocument();
|
||||||
|
expect(container.querySelectorAll(".wfm-mode-btn")).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses wfm-prompt-textarea class for the prompt textarea", async () => {
|
||||||
|
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([]);
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<WorkflowStepManager isOpen={true} onClose={onClose} addToast={addToast} />
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("add-workflow-step")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||||
|
|
||||||
|
expect(container.querySelector(".wfm-prompt-textarea")).toBeInTheDocument();
|
||||||
|
expect(container.querySelector(".wfm-prompt-header")).toBeInTheDocument();
|
||||||
|
expect(container.querySelector(".wfm-refine-btn")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses wfm-checkbox-label class for enabled toggle", async () => {
|
||||||
|
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([]);
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<WorkflowStepManager isOpen={true} onClose={onClose} addToast={addToast} />
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("add-workflow-step")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||||
|
|
||||||
|
expect(container.querySelector(".wfm-checkbox-label")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses wfm-form-actions class for form action buttons", async () => {
|
||||||
|
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([]);
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<WorkflowStepManager isOpen={true} onClose={onClose} addToast={addToast} />
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("add-workflow-step")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||||
|
|
||||||
|
expect(container.querySelector(".wfm-form-actions")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses wfm-footer and wfm-footer-add-btn classes for footer", async () => {
|
||||||
|
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([]);
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<WorkflowStepManager isOpen={true} onClose={onClose} addToast={addToast} />
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("add-workflow-step")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(container.querySelector(".wfm-footer")).toBeInTheDocument();
|
||||||
|
expect(container.querySelector(".wfm-footer-add-btn")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses wfm-no-scripts class when script mode has no scripts", async () => {
|
||||||
|
vi.mocked(fetchScripts).mockResolvedValueOnce({});
|
||||||
|
vi.mocked(fetchWorkflowSteps).mockResolvedValueOnce([]);
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<WorkflowStepManager isOpen={true} onClose={onClose} addToast={addToast} />
|
||||||
|
);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId("add-workflow-step")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId("add-workflow-step"));
|
||||||
|
fireEvent.click(screen.getByTestId("mode-script"));
|
||||||
|
|
||||||
|
expect(container.querySelector(".wfm-no-scripts")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -11791,6 +11791,338 @@ html .column.drag-over * {
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* === Workflow Step Manager Modal === */
|
||||||
|
.workflow-step-manager-modal {
|
||||||
|
width: 560px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-body {
|
||||||
|
padding: var(--space-md);
|
||||||
|
max-height: 70vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading / empty states */
|
||||||
|
.wfm-empty {
|
||||||
|
text-align: center;
|
||||||
|
padding: var(--space-xl);
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-loading {
|
||||||
|
text-align: center;
|
||||||
|
padding: var(--space-xl);
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tab navigation row */
|
||||||
|
.wfm-tab-row {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
padding-bottom: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-tab-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Step list */
|
||||||
|
.wfm-step-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Step card */
|
||||||
|
.wfm-step-card {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-step-card-top {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-step-card-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-step-card-title-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-step-card-name {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-step-card-desc {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-step-card-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
margin-left: var(--space-sm);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-delete-confirm {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Badges */
|
||||||
|
.wfm-badge {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-badge-enabled {
|
||||||
|
background: var(--status-success-bg, rgba(34, 197, 94, 0.15));
|
||||||
|
color: var(--status-success, #22c55e);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-badge-disabled {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-badge-prompt {
|
||||||
|
background: rgba(59, 130, 246, 0.15);
|
||||||
|
color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-badge-script {
|
||||||
|
background: rgba(168, 85, 247, 0.15);
|
||||||
|
color: #a855f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-badge-category {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Template cards */
|
||||||
|
.wfm-template-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-template-card {
|
||||||
|
padding: var(--space-md);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-template-inner {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-template-icon {
|
||||||
|
padding: var(--space-sm);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
color: var(--text);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-template-content {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-template-title-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-template-name {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-template-desc {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-bottom: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-template-add-btn {
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 4px 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Edit/create form */
|
||||||
|
.wfm-form {
|
||||||
|
padding: var(--space-md);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-form-title {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-form-fields {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-field label,
|
||||||
|
.wfm-field-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-field input[type="text"],
|
||||||
|
.wfm-field textarea,
|
||||||
|
.wfm-field select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: inherit;
|
||||||
|
outline: none;
|
||||||
|
transition:
|
||||||
|
border-color var(--transition-fast),
|
||||||
|
box-shadow var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-field input[type="text"]:focus,
|
||||||
|
.wfm-field textarea:focus,
|
||||||
|
.wfm-field select:focus {
|
||||||
|
border-color: var(--todo);
|
||||||
|
box-shadow: var(--focus-ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-field textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-field textarea.wfm-prompt-textarea {
|
||||||
|
font-family: var(--font-mono, monospace);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mode selector */
|
||||||
|
.wfm-mode-selector {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-mode-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
flex: 1;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prompt header row (label + refine button) */
|
||||||
|
.wfm-prompt-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-refine-btn {
|
||||||
|
font-size: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-refine-btn span {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No-scripts placeholder */
|
||||||
|
.wfm-no-scripts {
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkbox toggle */
|
||||||
|
.wfm-checkbox-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form actions */
|
||||||
|
.wfm-form-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.wfm-footer {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wfm-footer-add-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Spinner */
|
||||||
|
.wfm-spinner {
|
||||||
|
margin: 0 auto var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
.new-task-modal .form-group small {
|
.new-task-modal .form-group small {
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: var(--space-sm);
|
margin-top: var(--space-sm);
|
||||||
|
|||||||
Reference in New Issue
Block a user