feat(FN-1103): improve configured workflow steps experience
- Render a dedicated configured-steps state in WorkflowResultsTab with step count, descriptions, and phase badges when results are not yet available - Keep inline workflow step editing available from the configured state and results state, including add/remove and execution-order controls - Refine workflow step loading/edit behavior by aligning callback names and fetching step definitions by project context - Add dashboard styles for the configured workflow step cards and improved edit toggle button treatment - Expand WorkflowResultsTab and TaskDetailModal tests and add electron-updater type declarations to restore workspace build
This commit is contained in:
@@ -169,17 +169,46 @@ describe("WorkflowResultsTab", () => {
|
||||
expect(screen.getByText("No workflow steps configured for this task.")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows 'configured but not run' empty state when enabledWorkflowSteps is non-empty", () => {
|
||||
it("shows configured step details when enabledWorkflowSteps is non-empty and results are empty", async () => {
|
||||
render(
|
||||
<WorkflowResultsTab
|
||||
taskId="FN-001"
|
||||
results={[]}
|
||||
enabledWorkflowSteps={["WS-001", "WS-002"]}
|
||||
enabledWorkflowSteps={["WS-101", "WS-102"]}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("workflow-results-empty")).toBeInTheDocument();
|
||||
expect(screen.getByText("Workflow steps configured but haven't run yet.")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("workflow-configured-steps")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("workflow-configured-header")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("workflow-configured-count")).toHaveTextContent("2 steps");
|
||||
|
||||
const qaStep = await screen.findByTestId("workflow-configured-step-WS-101");
|
||||
const docsStep = await screen.findByTestId("workflow-configured-step-WS-102");
|
||||
|
||||
expect(qaStep).toHaveTextContent("QA Check");
|
||||
expect(qaStep).toHaveTextContent("Run test suite");
|
||||
expect(screen.getByTestId("workflow-configured-phase-WS-101")).toHaveTextContent("Pre-merge");
|
||||
|
||||
expect(docsStep).toHaveTextContent("Docs Review");
|
||||
expect(docsStep).toHaveTextContent("Review docs");
|
||||
expect(screen.getByTestId("workflow-configured-phase-WS-102")).toHaveTextContent("Post-merge");
|
||||
|
||||
expect(screen.getByText("Pre-merge steps run after implementation, before merge. Post-merge steps run after merge succeeds.")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("falls back to step ID and default description when definition is missing", () => {
|
||||
render(
|
||||
<WorkflowResultsTab
|
||||
taskId="FN-001"
|
||||
results={[]}
|
||||
enabledWorkflowSteps={["WS-unknown"]}
|
||||
/>,
|
||||
);
|
||||
|
||||
const fallbackStep = screen.getByTestId("workflow-configured-step-WS-unknown");
|
||||
expect(fallbackStep).toHaveTextContent("WS-unknown");
|
||||
expect(fallbackStep).toHaveTextContent("Step definition not found.");
|
||||
expect(screen.getByTestId("workflow-configured-phase-WS-unknown")).toHaveTextContent("Pre-merge");
|
||||
});
|
||||
|
||||
it("shows loading state when loading prop is true", () => {
|
||||
@@ -363,22 +392,43 @@ describe("WorkflowResultsTab", () => {
|
||||
});
|
||||
|
||||
describe("workflow step editing", () => {
|
||||
it("shows edit button when canEdit is true", () => {
|
||||
render(<WorkflowResultsTab taskId="FN-001" results={[]} canEdit />);
|
||||
it("shows edit button when canEdit is true and configured steps are present", () => {
|
||||
render(
|
||||
<WorkflowResultsTab
|
||||
taskId="FN-001"
|
||||
results={[]}
|
||||
canEdit
|
||||
enabledWorkflowSteps={["WS-101"]}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("workflow-steps-edit-toggle")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not show edit button when canEdit is false or undefined", () => {
|
||||
const { rerender } = render(<WorkflowResultsTab taskId="FN-001" results={[]} canEdit={false} />);
|
||||
const { rerender } = render(
|
||||
<WorkflowResultsTab
|
||||
taskId="FN-001"
|
||||
results={[]}
|
||||
canEdit={false}
|
||||
enabledWorkflowSteps={["WS-101"]}
|
||||
/>,
|
||||
);
|
||||
expect(screen.queryByTestId("workflow-steps-edit-toggle")).not.toBeInTheDocument();
|
||||
|
||||
rerender(<WorkflowResultsTab taskId="FN-001" results={[]} />);
|
||||
rerender(<WorkflowResultsTab taskId="FN-001" results={[]} enabledWorkflowSteps={["WS-101"]} />);
|
||||
expect(screen.queryByTestId("workflow-steps-edit-toggle")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows and hides workflow step checkboxes when edit is toggled", async () => {
|
||||
render(<WorkflowResultsTab taskId="FN-001" results={[]} canEdit />);
|
||||
render(
|
||||
<WorkflowResultsTab
|
||||
taskId="FN-001"
|
||||
results={[]}
|
||||
canEdit
|
||||
enabledWorkflowSteps={["WS-101"]}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByTestId("workflow-steps-editor")).not.toBeInTheDocument();
|
||||
|
||||
@@ -399,7 +449,7 @@ describe("WorkflowResultsTab", () => {
|
||||
taskId="FN-001"
|
||||
results={[]}
|
||||
canEdit
|
||||
enabledWorkflowSteps={[]}
|
||||
enabledWorkflowSteps={["WS-102"]}
|
||||
onWorkflowStepsChange={onWorkflowStepsChange}
|
||||
/>,
|
||||
);
|
||||
@@ -408,7 +458,7 @@ describe("WorkflowResultsTab", () => {
|
||||
const stepCheckbox = (await screen.findByTestId("workflow-step-checkbox-WS-101")).querySelector("input") as HTMLInputElement;
|
||||
fireEvent.click(stepCheckbox);
|
||||
|
||||
expect(onWorkflowStepsChange).toHaveBeenCalledWith(["WS-101"]);
|
||||
expect(onWorkflowStepsChange).toHaveBeenCalledWith(["WS-102", "WS-101"]);
|
||||
|
||||
onWorkflowStepsChange.mockClear();
|
||||
rerender(
|
||||
@@ -421,6 +471,10 @@ describe("WorkflowResultsTab", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
if (!screen.queryByTestId("workflow-steps-editor")) {
|
||||
fireEvent.click(screen.getByTestId("workflow-steps-edit-toggle"));
|
||||
}
|
||||
|
||||
const selectedCheckbox = (await screen.findByTestId("workflow-step-checkbox-WS-101")).querySelector("input") as HTMLInputElement;
|
||||
expect(selectedCheckbox.checked).toBe(true);
|
||||
fireEvent.click(selectedCheckbox);
|
||||
|
||||
@@ -108,11 +108,6 @@ export function WorkflowResultsTab({
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!canEdit) {
|
||||
setAllWorkflowSteps([]);
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
fetchWorkflowSteps(projectId)
|
||||
.then((steps) => {
|
||||
@@ -129,7 +124,7 @@ export function WorkflowResultsTab({
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [canEdit, projectId]);
|
||||
}, [projectId]);
|
||||
|
||||
const selectedWorkflowSteps = enabledWorkflowSteps ?? [];
|
||||
|
||||
@@ -160,7 +155,7 @@ export function WorkflowResultsTab({
|
||||
setExpandedOutputs((prev) => ({ ...prev, [stepId]: !prev[stepId] }));
|
||||
};
|
||||
|
||||
const toggleWorkflowStep = useCallback((stepId: string, checked: boolean) => {
|
||||
const toggleStep = useCallback((stepId: string, checked: boolean) => {
|
||||
if (!onWorkflowStepsChange) return;
|
||||
|
||||
if (checked) {
|
||||
@@ -195,6 +190,114 @@ export function WorkflowResultsTab({
|
||||
}, [onWorkflowStepsChange, selectedWorkflowSteps]);
|
||||
|
||||
const hasResults = results.length > 0;
|
||||
const hasConfiguredSteps = selectedWorkflowSteps.length > 0;
|
||||
|
||||
useEffect(() => {
|
||||
if (!canEdit) {
|
||||
setIsEditing(false);
|
||||
}
|
||||
}, [canEdit]);
|
||||
|
||||
const configuredSteps = useMemo(() => {
|
||||
return selectedWorkflowSteps.map((stepId) => {
|
||||
const stepInfo = workflowStepLookup.get(stepId);
|
||||
return {
|
||||
id: stepId,
|
||||
name: stepInfo?.name || stepId,
|
||||
description: stepInfo?.description || "Step definition not found.",
|
||||
phase: stepInfo?.phase || "pre-merge",
|
||||
} as WorkflowStepOption;
|
||||
});
|
||||
}, [selectedWorkflowSteps, workflowStepLookup]);
|
||||
|
||||
const renderEditor = () => {
|
||||
if (!canEdit || !isEditing || loading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="workflow-results-editor" data-testid="workflow-steps-editor">
|
||||
<small style={{ marginBottom: "8px", display: "block" }}>
|
||||
Select steps to run after task implementation completes
|
||||
</small>
|
||||
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
|
||||
{workflowStepOptions.map((step) => (
|
||||
<label
|
||||
key={step.id}
|
||||
className="checkbox-label"
|
||||
style={{ display: "flex", alignItems: "flex-start", gap: "8px" }}
|
||||
data-testid={step.id === "browser-verification"
|
||||
? "browser-verification-checkbox"
|
||||
: `workflow-step-checkbox-${step.id}`}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedWorkflowSteps.includes(step.id)}
|
||||
onChange={(event) => toggleStep(step.id, event.target.checked)}
|
||||
style={{ marginTop: "2px" }}
|
||||
/>
|
||||
<div>
|
||||
<span style={{ fontWeight: 500, fontSize: "13px" }}>
|
||||
{step.name}
|
||||
{phaseBadge(step.phase, step.id, "workflow-step-phase")}
|
||||
</span>
|
||||
<div style={{ fontSize: "12px", color: "var(--text-secondary)", marginTop: "2px" }}>
|
||||
{step.description}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{selectedWorkflowSteps.length > 1 && (
|
||||
<div className="workflow-step-order" data-testid="workflow-step-order">
|
||||
<small className="workflow-step-order-label">Execution order:</small>
|
||||
{selectedWorkflowSteps.map((stepId, index) => {
|
||||
const stepInfo = workflowStepLookup.get(stepId);
|
||||
return (
|
||||
<div key={stepId} className="workflow-step-order-item" data-testid={`workflow-step-order-item-${stepId}`}>
|
||||
<span className="workflow-step-order-number">{index + 1}</span>
|
||||
<span className="workflow-step-order-name">{stepInfo?.name || stepId}</span>
|
||||
<div className="workflow-step-order-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-sm"
|
||||
onClick={() => moveWorkflowStepUp(index)}
|
||||
disabled={index === 0}
|
||||
data-testid={`workflow-step-move-up-${stepId}`}
|
||||
title="Move up"
|
||||
>
|
||||
<ChevronUp size={14} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-sm"
|
||||
onClick={() => moveWorkflowStepDown(index)}
|
||||
disabled={index === selectedWorkflowSteps.length - 1}
|
||||
data-testid={`workflow-step-move-down-${stepId}`}
|
||||
title="Move down"
|
||||
>
|
||||
<ChevronDown size={14} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-sm"
|
||||
onClick={() => removeWorkflowStep(stepId)}
|
||||
data-testid={`workflow-step-remove-${stepId}`}
|
||||
title="Remove"
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderResults = () => {
|
||||
if (loading) {
|
||||
@@ -207,14 +310,9 @@ export function WorkflowResultsTab({
|
||||
}
|
||||
|
||||
if (!hasResults) {
|
||||
const hasConfiguredSteps = selectedWorkflowSteps.length > 0;
|
||||
return (
|
||||
<div className="workflow-results-empty" data-testid="workflow-results-empty">
|
||||
<p>
|
||||
{hasConfiguredSteps
|
||||
? "Workflow steps configured but haven't run yet."
|
||||
: "No workflow steps configured for this task."}
|
||||
</p>
|
||||
<p>No workflow steps configured for this task.</p>
|
||||
<p className="workflow-results-empty-hint">
|
||||
Pre-merge steps run after implementation, before merge. Post-merge steps run after merge succeeds.
|
||||
</p>
|
||||
@@ -310,109 +408,79 @@ export function WorkflowResultsTab({
|
||||
);
|
||||
};
|
||||
|
||||
const showEditUI = !!canEdit && isEditing;
|
||||
const editButton = canEdit ? (
|
||||
<button
|
||||
type="button"
|
||||
className="modal-edit-btn workflow-results-edit-toggle"
|
||||
onClick={() => setIsEditing((prev) => !prev)}
|
||||
data-testid="workflow-steps-edit-toggle"
|
||||
aria-label={isEditing ? "Done editing workflow steps" : "Edit workflow steps"}
|
||||
title={isEditing ? "Done" : "Edit"}
|
||||
>
|
||||
{isEditing ? (
|
||||
<>
|
||||
<Check size={14} />
|
||||
Done
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Pencil size={14} />
|
||||
Edit
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
) : null;
|
||||
|
||||
const showConfiguredStepsState = !loading && !hasResults && hasConfiguredSteps;
|
||||
const showEditHeaderForResults = canEdit && hasResults;
|
||||
|
||||
return (
|
||||
<div className="workflow-results-tab" data-task-id={taskId}>
|
||||
{canEdit && (
|
||||
<div className="workflow-results-edit-header" data-testid="workflow-results-edit-header">
|
||||
<h4>Workflow Steps</h4>
|
||||
<button
|
||||
type="button"
|
||||
className="modal-edit-btn"
|
||||
onClick={() => setIsEditing((prev) => !prev)}
|
||||
data-testid="workflow-steps-edit-toggle"
|
||||
aria-label={isEditing ? "Done editing workflow steps" : "Edit workflow steps"}
|
||||
title={isEditing ? "Done" : "Edit"}
|
||||
>
|
||||
{isEditing ? <Check size={14} /> : <Pencil size={14} />}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{showConfiguredStepsState ? (
|
||||
<div className="workflow-configured-steps" data-testid="workflow-configured-steps">
|
||||
<div className="workflow-configured-header" data-testid="workflow-configured-header">
|
||||
<div className="workflow-configured-title-row">
|
||||
<h4>Configured Workflow Steps</h4>
|
||||
<span className="workflow-configured-count" data-testid="workflow-configured-count">
|
||||
{configuredSteps.length} step{configuredSteps.length === 1 ? "" : "s"}
|
||||
</span>
|
||||
</div>
|
||||
{editButton}
|
||||
</div>
|
||||
|
||||
{(!showEditUI || hasResults || loading) && renderResults()}
|
||||
|
||||
{showEditUI && !loading && (
|
||||
<div className="workflow-results-editor" data-testid="workflow-steps-editor">
|
||||
<small style={{ marginBottom: "8px", display: "block" }}>
|
||||
Select steps to run after task implementation completes
|
||||
</small>
|
||||
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "6px" }}>
|
||||
{workflowStepOptions.map((step) => (
|
||||
<label
|
||||
<div className="workflow-configured-list" data-testid="workflow-configured-list">
|
||||
{configuredSteps.map((step) => (
|
||||
<div
|
||||
key={step.id}
|
||||
className="checkbox-label"
|
||||
style={{ display: "flex", alignItems: "flex-start", gap: "8px" }}
|
||||
data-testid={step.id === "browser-verification"
|
||||
? "browser-verification-checkbox"
|
||||
: `workflow-step-checkbox-${step.id}`}
|
||||
className="workflow-configured-item"
|
||||
data-testid={`workflow-configured-step-${step.id}`}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedWorkflowSteps.includes(step.id)}
|
||||
onChange={(event) => toggleWorkflowStep(step.id, event.target.checked)}
|
||||
style={{ marginTop: "2px" }}
|
||||
/>
|
||||
<div>
|
||||
<span style={{ fontWeight: 500, fontSize: "13px" }}>
|
||||
{step.name}
|
||||
{phaseBadge(step.phase, step.id, "workflow-step-phase")}
|
||||
</span>
|
||||
<div style={{ fontSize: "12px", color: "var(--text-secondary)", marginTop: "2px" }}>
|
||||
{step.description}
|
||||
</div>
|
||||
<div className="workflow-configured-name">
|
||||
{step.name}
|
||||
{phaseBadge(step.phase, step.id, "workflow-configured-phase")}
|
||||
</div>
|
||||
</label>
|
||||
<p className="workflow-configured-description">{step.description}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{selectedWorkflowSteps.length > 1 && (
|
||||
<div className="workflow-step-order" data-testid="workflow-step-order">
|
||||
<small className="workflow-step-order-label">Execution order:</small>
|
||||
{selectedWorkflowSteps.map((stepId, index) => {
|
||||
const stepInfo = workflowStepLookup.get(stepId);
|
||||
return (
|
||||
<div key={stepId} className="workflow-step-order-item" data-testid={`workflow-step-order-item-${stepId}`}>
|
||||
<span className="workflow-step-order-number">{index + 1}</span>
|
||||
<span className="workflow-step-order-name">{stepInfo?.name || stepId}</span>
|
||||
<div className="workflow-step-order-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-sm"
|
||||
onClick={() => moveWorkflowStepUp(index)}
|
||||
disabled={index === 0}
|
||||
data-testid={`workflow-step-move-up-${stepId}`}
|
||||
title="Move up"
|
||||
>
|
||||
<ChevronUp size={14} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-sm"
|
||||
onClick={() => moveWorkflowStepDown(index)}
|
||||
disabled={index === selectedWorkflowSteps.length - 1}
|
||||
data-testid={`workflow-step-move-down-${stepId}`}
|
||||
title="Move down"
|
||||
>
|
||||
<ChevronDown size={14} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-sm"
|
||||
onClick={() => removeWorkflowStep(stepId)}
|
||||
data-testid={`workflow-step-remove-${stepId}`}
|
||||
title="Remove"
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<p className="workflow-results-empty-hint">
|
||||
Pre-merge steps run after implementation, before merge. Post-merge steps run after merge succeeds.
|
||||
</p>
|
||||
|
||||
{renderEditor()}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{showEditHeaderForResults && (
|
||||
<div className="workflow-results-edit-header" data-testid="workflow-results-edit-header">
|
||||
<h4>Workflow Steps</h4>
|
||||
{editButton}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{renderResults()}
|
||||
{renderEditor()}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4241,7 +4241,7 @@ describe("TaskDetailModal", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("renders empty state when workflow results are empty", async () => {
|
||||
it("renders configured workflow steps state when results are empty", async () => {
|
||||
const { fetchWorkflowResults } = await import("../../api");
|
||||
const mockFetch = vi.mocked(fetchWorkflowResults);
|
||||
mockFetch.mockResolvedValueOnce([]);
|
||||
@@ -4261,8 +4261,8 @@ describe("TaskDetailModal", () => {
|
||||
fireEvent.click(screen.getByText("Workflow"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("workflow-results-empty")).toBeTruthy();
|
||||
expect(screen.getByText("Workflow steps configured but haven't run yet.")).toBeTruthy();
|
||||
expect(screen.getByTestId("workflow-configured-steps")).toBeTruthy();
|
||||
expect(screen.getByTestId("workflow-configured-step-WS-001")).toHaveTextContent("WS-001");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -19236,6 +19236,92 @@ html .column.drag-over * {
|
||||
color: var(--text-primary, #e6edf3);
|
||||
}
|
||||
|
||||
.workflow-results-edit-toggle {
|
||||
width: auto;
|
||||
height: 30px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid var(--border, #30363d);
|
||||
border-radius: 6px;
|
||||
background: var(--surface-elevated, #161b22);
|
||||
color: var(--text-muted, #8b949e);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.workflow-results-edit-toggle:hover {
|
||||
background: var(--surface, #21262d);
|
||||
color: var(--text-primary, #e6edf3);
|
||||
}
|
||||
|
||||
.workflow-results-edit-toggle svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.workflow-configured-steps {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm, 8px);
|
||||
}
|
||||
|
||||
.workflow-configured-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-sm, 8px);
|
||||
}
|
||||
|
||||
.workflow-configured-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm, 8px);
|
||||
}
|
||||
|
||||
.workflow-configured-title-row h4 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: var(--text-primary, #e6edf3);
|
||||
}
|
||||
|
||||
.workflow-configured-count {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--border, #30363d);
|
||||
color: var(--text-muted, #8b949e);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.workflow-configured-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm, 8px);
|
||||
}
|
||||
|
||||
.workflow-configured-item {
|
||||
border: 1px solid var(--border, #30363d);
|
||||
border-radius: 8px;
|
||||
padding: var(--space-md, 16px);
|
||||
background: var(--surface-elevated, #161b22);
|
||||
}
|
||||
|
||||
.workflow-configured-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: var(--text-primary, #e6edf3);
|
||||
}
|
||||
|
||||
.workflow-configured-description {
|
||||
margin: var(--space-xs, 4px) 0 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
color: var(--text-secondary, #c9d1d9);
|
||||
}
|
||||
|
||||
.workflow-results-editor {
|
||||
border: 1px solid var(--border, #30363d);
|
||||
border-radius: 8px;
|
||||
|
||||
17
packages/desktop/src/electron-updater.d.ts
vendored
Normal file
17
packages/desktop/src/electron-updater.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
declare module "electron-updater" {
|
||||
export interface UpdateInfo {
|
||||
version?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface AutoUpdater {
|
||||
autoDownload: boolean;
|
||||
autoInstallOnAppQuit: boolean;
|
||||
on(event: "update-available", listener: (info: UpdateInfo) => void): this;
|
||||
on(event: "update-downloaded", listener: (info: UpdateInfo) => void): this;
|
||||
on(event: "error", listener: (error: Error) => void): this;
|
||||
checkForUpdates(): Promise<unknown>;
|
||||
}
|
||||
|
||||
export const autoUpdater: AutoUpdater;
|
||||
}
|
||||
Reference in New Issue
Block a user