diff --git a/app/src/components/detail/JobFields.tsx b/app/src/components/detail/JobFields.tsx
index 07f2176..152b464 100644
--- a/app/src/components/detail/JobFields.tsx
+++ b/app/src/components/detail/JobFields.tsx
@@ -58,8 +58,16 @@ export function JobFields({ job, onSave, onDelete }: Props) {
return (
-
-
+
+
{runnerLabel(runner)} ·
) => Promise
onDelete?: (name: string) => Promise
}
-/** Edit a program's catalog config (source identity), not how it's deployed. */
+type Obj = Record
+
+/** Edit a program's catalog config (its source identity), not how it's deployed. */
export function ProgramFields({ program, onSave, onDelete }: Props) {
const m = program.manifest
const [saving, setSaving] = useState(false)
@@ -26,11 +33,24 @@ export function ProgramFields({ program, onSave, onDelete }: Props) {
const [ref, setRef] = useState((m.ref as string) ?? "")
const [deps, setDeps] = useState(((m.system_dependencies as string[]) ?? []).join(", "))
+ const readVerb = (verb: string): string => {
+ if (verb === "build") {
+ const c = (m.build as { commands?: string[][] } | undefined)?.commands
+ return (c?.[0] ?? []).join(" ")
+ }
+ const cmds = (m.commands as Record | undefined) ?? {}
+ const key = verb === "type-check" && !cmds["type-check"] ? "type_check" : verb
+ return (cmds[key]?.[0] ?? []).join(" ")
+ }
+ const [cmds, setCmds] = useState>(() =>
+ Object.fromEntries(VERBS.map((v) => [v, readVerb(v)])),
+ )
+
const handleSave = async () => {
setSaving(true)
setSaved(false)
try {
- const config: Record = { ...m }
+ const config: Obj = { ...m }
delete config.id
config.description = description || undefined
config.source = source || undefined
@@ -39,10 +59,20 @@ export function ProgramFields({ program, onSave, onDelete }: Props) {
config.version = version || undefined
config.repo = repo || undefined
config.ref = ref || undefined
- config.system_dependencies = deps
- .split(",")
- .map((d) => d.trim())
- .filter(Boolean)
+ config.system_dependencies = deps.split(",").map((d) => d.trim()).filter(Boolean)
+
+ const commands: Record = {}
+ let buildArgv: string[][] | null = null
+ for (const v of VERBS) {
+ const s = (cmds[v] ?? "").trim()
+ if (!s) continue
+ const argv: string[][] = [s.split(/\s+/)]
+ if (v === "build") buildArgv = argv
+ else commands[v] = argv
+ }
+ if (buildArgv) config.build = { ...((m.build as Obj) ?? {}), commands: buildArgv }
+ config.commands = Object.keys(commands).length ? commands : undefined
+
await onSave(program.id, config)
setSaved(true)
setTimeout(() => setSaved(false), 2000)
@@ -53,9 +83,24 @@ export function ProgramFields({ program, onSave, onDelete }: Props) {
return (
-
-
-
+
+
+
-
+
-
-
-
-
+
+
+
+
- {program.commands && Object.keys(program.commands).length > 0 && (
-
-
- {Object.entries(program.commands).map(([verb, cmds]) => (
-
- {verb}
- {cmds.map((a) => a.join(" ")).join(" && ")}
-
- ))}
-
-
- )}
+
+
+ {VERBS.map((verb) => (
+
+ {verb}
+ setCmds((c) => ({ ...c, [verb]: e.target.value }))}
+ placeholder={stack ? "(stack default)" : "—"}
+ className="flex-1 bg-black/30 border border-[var(--border)] rounded px-2 py-1 text-xs font-mono focus:outline-none focus:border-[var(--primary)]"
+ />
+
+ ))}
+
+
-
+
{runnerLabel(runner)} ·
-
-
-
-
+
+
+
+
{envEditor}
- {children}
+
+ {children}
+ {hint &&
{hint}
}
+
)
}
@@ -21,6 +32,7 @@ export function TextField({
placeholder,
mono,
width,
+ hint,
}: {
label: string
value: string
@@ -28,9 +40,10 @@ export function TextField({
placeholder?: string
mono?: boolean
width?: string
+ hint?: string
}) {
return (
-
+
onChange(e.target.value)}