feat(profiles): add typed live step config profiles

Profiles store script + default config per owner. Workflow steps reference
them with profile:, overlay keys win, and the UI marks overrides. Profile
name is immutable after create so YAML refs stay stable.

Co-authored-by: Nasyarobby Putra <nasyarobby@gmail.com>
This commit is contained in:
Cursor Agent
2026-08-21 06:12:49 +00:00
co-authored by nsrb
parent 210fdb2244
commit e84432a492
25 changed files with 1512 additions and 123 deletions
@@ -11,12 +11,14 @@ import { contextFromMeta, prettyJson } from "../../lib/script.js";
import { ConfigFields } from "./ConfigFields.jsx";
import { ConfigTooltip, configValueText, FieldLabel, previewConfigValue, SchemaTooltip } from "./FieldHelp.jsx";
import { ScriptIcon } from "../ScriptIcon.jsx";
import { configHasOverlay, mergeProfileConfig } from "../../lib/profile.js";
export function ScriptCard({
step,
index,
otherSteps,
scriptsByName,
profilesByName,
onChange,
onRemove,
disabled,
@@ -35,25 +37,33 @@ export function ScriptCard({
};
const [tryOpen, setTryOpen] = useState(false);
const [expanded, setExpanded] = useState(false);
const listed = scriptsByName?.get(step.script);
const profile = step.profile && profilesByName?.get(step.profile);
const scriptName = profile?.script || step.script;
const listed = scriptsByName?.get(scriptName);
const meta = listed?.meta ?? null;
const inheritedConfig = profile?.config ?? null;
const mergedConfig = profile
? mergeProfileConfig(profile.config, step.config)
: step.config;
const overridden = Boolean(step.profile && configHasOverlay(step.config));
const duplicateId =
step.id && otherSteps.some((s) => s.id === step.id && s.uiId !== step.uiId);
const preview = previewConfigValue(step.config, meta?.previewConfigKey);
const previewFull = configValueText(step.config, meta?.previewConfigKey);
const baseName = step.kind === "set" ? "set" : step.script || "untitled";
const preview = previewConfigValue(mergedConfig, meta?.previewConfigKey);
const previewFull = configValueText(mergedConfig, meta?.previewConfigKey);
const baseName = step.kind === "set" ? "set" : scriptName || "untitled";
const titleFull = previewFull ? `${baseName} (${previewFull})` : baseName;
const setConfig =
step.kind === "set" ? { expression: step.expression ?? "" } : null;
const missingProfile = Boolean(step.profile) && !profile;
return (
<article
ref={setNodeRef}
style={style}
className={`card bg-base-100 border border-primary ${
isDragging ? "z-40" : "z-0 hover:z-30 focus-within:z-30"
}`}
className={`card bg-base-100 border ${
step.profile ? "border-accent" : "border-primary"
} ${isDragging ? "z-40" : "z-0 hover:z-30 focus-within:z-30"}`}
>
<div className={`card-body gap-3 ${expanded ? "p-4" : "p-3"}`}>
<div className="flex items-center gap-2">
@@ -67,8 +77,8 @@ export function ScriptCard({
>
<LuGripVertical className="size-4 opacity-60" />
</button>
{step.kind === "script" && step.script ? (
<ScriptIcon name={step.script} hasIcon={listed?.hasIcon} className="size-8 shrink-0" />
{step.kind === "script" && scriptName ? (
<ScriptIcon name={scriptName} hasIcon={listed?.hasIcon} className="size-8 shrink-0" />
) : null}
<button
type="button"
@@ -87,10 +97,29 @@ export function ScriptCard({
</span>
) : null}
</h3>
{step.profile ? (
<span
className={`badge badge-sm shrink-0 font-mono font-normal ${
overridden ? "badge-warning" : "badge-accent"
}`}
title={
overridden
? `profile ${step.profile} + overridden`
: `profile ${step.profile}`
}
>
{overridden ? `profile ${step.profile} + overridden` : `profile ${step.profile}`}
</span>
) : null}
{step.id ? (
<span className="badge badge-ghost badge-sm font-mono shrink-0">{step.id}</span>
) : null}
</div>
{expanded && step.kind === "script" && missingProfile ? (
<p className="text-sm text-error mt-1">
Profile {step.profile} not found for owner {owner || "?"}
</p>
) : null}
{expanded && step.kind === "script" && meta?.description ? (
<p className="text-sm opacity-70 mt-1">{meta.description}</p>
) : null}
@@ -104,10 +133,10 @@ export function ScriptCard({
<SchemaTooltip label="Input" fields={meta?.input} />
) : null}
{!expanded ? (
<ConfigTooltip config={step.kind === "set" ? setConfig : (step.config ?? {})} />
<ConfigTooltip config={step.kind === "set" ? setConfig : (mergedConfig ?? {})} />
) : null}
<div className="card-actions shrink-0">
{step.kind === "script" && step.script ? (
{step.kind === "script" && scriptName ? (
<button
type="button"
className="btn btn-ghost btn-xs"
@@ -158,8 +187,9 @@ export function ScriptCard({
</div>
) : (
<ConfigFields
script={step.script}
script={scriptName}
config={step.config}
inheritedConfig={inheritedConfig}
meta={meta}
disabled={disabled}
workflows={workflows}
@@ -211,8 +241,8 @@ export function ScriptCard({
</div>
{tryOpen ? (
<ScriptTryDialog
script={step.script}
config={step.config}
script={scriptName}
config={mergedConfig}
meta={meta}
owner={owner}
onClose={() => setTryOpen(false)}