Files
PaperClipAI/ui/src/lib/routine-run-display.ts
T
DottaandClaude Opus 4.8 ab4d77c4c1 feat(ui): routine detail variation C polish follow-ups (PAP-10735)
Implements the five §3.x spec-completion items flagged in PAP-10735:

1. Runs row subtitle (§3.6): rebuild RunsSection on <EntityRow> with a
   subtitle showing inline variable values for successful runs and the
   failure reason for failed runs; de-dupe the redundant trigger label
   when label === kind (new ui/src/lib/routine-run-display.ts).
2. Runs filter chips (§3.6): Source/Status/Date selects + <FilterBar>
   active pills + Clear, with a filtered-empty state.
3. Delivery next-5-fires preview (§3.5): new tz-aware ui/src/lib/cron-fires.ts
   plus a pure previewFirePolicies() that annotates each fire with how the
   chosen concurrency policy treats it (queued/coalesced/skipped).
4. Secrets (§3.4): extend EnvVarEditor with recentlyUsedSecrets quick-bind
   chips and replace the native <select>s with shadcn <Select> for parity.
5. Triggers (§3.2): convert the always-open add form into a + New trigger
   expand-on-click drawer.

Tests: new cron-fires + routine-run-display unit suites (30 cases);
existing routine/EnvVarEditor-consumer tests stay green; tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 22:59:27 +00:00

55 lines
1.9 KiB
TypeScript

import type { RoutineRunSummary, RoutineVariable } from "@paperclipai/shared";
/**
* Format a single resolved variable value for the runs-row subtitle (§3.6).
* Strings are quoted (`customer="Acme"`); numbers/booleans rendered bare.
*/
function formatVariableValue(value: unknown): string {
if (typeof value === "string") return `"${value}"`;
if (typeof value === "number" || typeof value === "boolean") return String(value);
if (value == null) return "—";
try {
return JSON.stringify(value);
} catch {
return String(value);
}
}
/**
* The de-duped trigger label for a run. The kind chip already states the kind,
* so when a trigger has no custom label (`label` falls back to `kind`) we drop
* the redundant text rather than re-stating it.
*/
export function dedupedTriggerLabel(
trigger: Pick<RoutineRunSummary["trigger"] & object, "kind" | "label"> | null | undefined,
): string | null {
if (!trigger) return null;
const label = trigger.label?.trim();
if (!label) return null;
if (label === trigger.kind) return null;
return label;
}
/**
* Subtitle line for a run row (§3.6):
* - failed runs show the failure reason ("why" without clicking through);
* - other runs show the inline resolved variable values (e.g. `customer="Acme"`).
* Returns an empty string when there is nothing meaningful to show.
*/
export function runRowSubtitle(
run: Pick<RoutineRunSummary, "status" | "failureReason" | "triggerPayload">,
variables: readonly RoutineVariable[] | null | undefined,
): string {
if (run.status === "failed") {
return run.failureReason?.trim() || "Run failed";
}
const payload = run.triggerPayload;
if (!payload || typeof payload !== "object") return "";
const parts: string[] = [];
for (const variable of variables ?? []) {
if (!(variable.name in payload)) continue;
parts.push(`${variable.name}=${formatVariableValue((payload as Record<string, unknown>)[variable.name])}`);
}
return parts.join(", ");
}