diff --git a/desktop/src/features/workflows/ui/AuthorGridPicker.tsx b/desktop/src/features/workflows/ui/AuthorGridPicker.tsx
index a12105e14..012fef2ab 100644
--- a/desktop/src/features/workflows/ui/AuthorGridPicker.tsx
+++ b/desktop/src/features/workflows/ui/AuthorGridPicker.tsx
@@ -145,7 +145,8 @@ export function AuthorGridPicker({
);
function selectAuthor(pubkey: string) {
- onChange(pubkey.toLowerCase());
+ const normalizedPubkey = pubkey.toLowerCase();
+ onChange(normalizedPubkey === normalizedValue ? "" : normalizedPubkey);
}
function loadNextPage() {
diff --git a/desktop/src/features/workflows/ui/WorkflowCard.tsx b/desktop/src/features/workflows/ui/WorkflowCard.tsx
index 603391a6d..3b9f26f0b 100644
--- a/desktop/src/features/workflows/ui/WorkflowCard.tsx
+++ b/desktop/src/features/workflows/ui/WorkflowCard.tsx
@@ -46,6 +46,7 @@ import {
getWorkflowTriggerSummary,
getWorkflowTriggerType,
} from "./workflowDefinition";
+import { TriggerDescriptionText } from "./WorkflowNodeDescriptions";
import { useWorkflowTriggerPresentation } from "./useWorkflowTriggerPresentation";
type WorkflowCardProps = {
@@ -182,6 +183,28 @@ function ReactionLabelText({
return parts.length > 0 ? parts : text;
}
+function TriggerCardText({
+ authorLoading,
+ messageLoading,
+ reactions,
+ text,
+}: {
+ authorLoading?: boolean;
+ messageLoading?: boolean;
+ reactions: ReadonlyArray<{ emoji: string; url?: string }>;
+ text: string;
+}) {
+ return authorLoading || messageLoading ? (
+
+ ) : (
+
+ );
+}
+
export function WorkflowCard({
workflow,
channelName,
@@ -348,14 +371,21 @@ export function WorkflowCard({
{triggerSummary ? (
-
) : null}
-
+
{description ? (
diff --git a/desktop/src/features/workflows/ui/WorkflowConditionBuilder.tsx b/desktop/src/features/workflows/ui/WorkflowConditionBuilder.tsx
index 690cc7d1a..33c30ee95 100644
--- a/desktop/src/features/workflows/ui/WorkflowConditionBuilder.tsx
+++ b/desktop/src/features/workflows/ui/WorkflowConditionBuilder.tsx
@@ -1,8 +1,11 @@
+import { ChevronRight } from "lucide-react";
import * as React from "react";
+import { useUsersBatchQuery } from "@/features/profile/hooks";
+import { ProfileAvatar } from "@/features/profile/ui/ProfileAvatar";
import { useIdentityQuery } from "@/shared/api/hooks";
-import type { Channel } from "@/shared/api/types";
-import { cn } from "@/shared/lib/cn";
+import type { Channel, UserProfileSummary } from "@/shared/api/types";
+import { truncatePubkey } from "@/shared/lib/pubkey";
import { Input } from "@/shared/ui/input";
import { AuthorGridPicker } from "./AuthorGridPicker";
import { MessageIdPicker } from "./MessageIdPicker";
@@ -13,7 +16,6 @@ import {
conditionFieldsForTrigger,
conditionOperatorsForField,
conditionOperatorNeedsValue,
- CUSTOM_CONDITION_FIELD,
defaultConditionOperatorForField,
normalizeWebhookField,
parseConditionExpressions,
@@ -53,6 +55,10 @@ type ConditionEditorState = {
editors: ParsedConditionExpression[];
};
+type ActiveConditionEditor =
+ | { kind: "field"; draft: ParsedConditionExpression }
+ | { kind: "custom"; draft: string };
+
function normalizedEditor(
editor: ParsedConditionExpression,
): ParsedConditionExpression {
@@ -105,13 +111,87 @@ function valuePlaceholder(field: string): string {
}
}
-function ConditionEditorControls({
+function compactValue(value: string): string {
+ const trimmed = value.trim();
+ if (trimmed.length <= 18) return trimmed;
+ return `${trimmed.slice(0, 10)}…${trimmed.slice(-5)}`;
+}
+
+function editorSummary(editor: ParsedConditionExpression): string {
+ const operator = operatorLabel(
+ editor.field,
+ editor.operator,
+ conditionOperatorsForField(editor.field).length === 2,
+ );
+ if (!conditionOperatorNeedsValue(editor.operator)) return operator;
+ const value = compactValue(editor.value);
+ if (!value) return "Off";
+ return editor.field === "trigger_text"
+ ? `${operator} “${value}”`
+ : `${operator} ${value}`;
+}
+
+function authorSummaryLabel(
+ pubkey: string,
+ profile?: UserProfileSummary | null,
+): string {
+ return (
+ profile?.displayName?.trim() ||
+ profile?.name?.trim() ||
+ profile?.nip05Handle?.trim() ||
+ truncatePubkey(pubkey)
+ );
+}
+
+function AuthorConditionSummary({
+ editor,
+ profile,
+}: {
+ editor: ParsedConditionExpression;
+ profile?: UserProfileSummary | null;
+}) {
+ const label = authorSummaryLabel(editor.value, profile);
+ const isExcluded = editor.operator === "not_equals";
+
+ return (
+
+
+
+ {isExcluded ? (
+
+
+
+
+
+
+
+
+
+
+ ) : null}
+
+
+ {isExcluded ? "Excluded author: " : "Selected author: "}
+ {label}
+
+
+ );
+}
+
+function ConditionEditorFields({
channelId,
disabled,
editor,
idPrefix,
knownAuthorPubkeys,
- label,
onChange,
}: {
channelId?: string | null;
@@ -119,7 +199,6 @@ function ConditionEditorControls({
editor: ParsedConditionExpression;
idPrefix: string;
knownAuthorPubkeys: string[];
- label: string;
onChange: (editor: ParsedConditionExpression) => void;
}) {
const needsValue = conditionOperatorNeedsValue(editor.operator);
@@ -132,13 +211,10 @@ function ConditionEditorControls({
const controlIdPrefix = `${idPrefix}-${editor.field}`;
return (
-