Allow clicking a declared param to load it into the form for editing

Mirrors Metrics.tsx's click-to-edit pattern: clicking a param chip
populates the declaration form, shows an "Editing" indicator, and swaps
the submit button for Save changes / Cancel. The name field is locked
while editing since declare_param overwrites in place by name — renaming
would silently orphan the original and anything referencing it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
This commit is contained in:
Hugh Kaznowski
2026-09-05 23:32:06 +01:00
co-authored by Claude Sonnet 5
parent 5e30e02a78
commit ed9aa8eb45
+69 -19
View File
@@ -43,6 +43,11 @@ export function Params({ params, onDeclared }: { params: ParamDeclaration[]; onD
const [submitError, setSubmitError] = useState<string | null>(null) const [submitError, setSubmitError] = useState<string | null>(null)
const [submitting, setSubmitting] = useState(false) const [submitting, setSubmitting] = useState(false)
const [collapsed, setCollapsed] = useSectionCollapsed('params') const [collapsed, setCollapsed] = useSectionCollapsed('params')
// Name of the declaration currently loaded into the form for editing, or `null` when the form is
// declaring a brand-new param. The name field is locked while editing — `declareParam` overwrites
// in place by name, so changing it here would silently create a second param and orphan the
// original (and anything referencing it by name) rather than renaming it.
const [editingName, setEditingName] = useState<string | null>(null)
const isBoolean = kind === 'boolean' const isBoolean = kind === 'boolean'
const isElapsedSinceLast = source === 'elapsed_since_last' const isElapsedSinceLast = source === 'elapsed_since_last'
@@ -63,6 +68,30 @@ export function Params({ params, onDeclared }: { params: ParamDeclaration[]; onD
if (next === 'elapsed_since_last') setKind('duration') if (next === 'elapsed_since_last') setKind('duration')
} }
function resetForm() {
setEditingName(null)
setName('')
setRegex('')
setCaptureGroup('')
setJsonFieldPath('')
setSource('regex')
setKind(EMPTY_KIND)
setSubmitError(null)
}
// Loads `param`'s current values into the declaration form for editing — a click handler for a
// declared-param badge.
function editParam(param: ParamDeclaration) {
setEditingName(param.name)
setName(param.name)
setRegex(param.message_match_regex)
setCaptureGroup(param.capture_group)
setJsonFieldPath(param.json_field_path ?? '')
setSource(param.elapsed_since_last ? 'elapsed_since_last' : param.json_field_path ? 'json_field' : 'regex')
setKind(param.kind)
setSubmitError(null)
}
async function handleSubmit(e: React.FormEvent) { async function handleSubmit(e: React.FormEvent) {
e.preventDefault() e.preventDefault()
setSubmitError(null) setSubmitError(null)
@@ -79,15 +108,10 @@ export function Params({ params, onDeclared }: { params: ParamDeclaration[]; onD
kind: effectiveSource === 'elapsed_since_last' ? 'duration' : kind, kind: effectiveSource === 'elapsed_since_last' ? 'duration' : kind,
elapsed_since_last: effectiveSource === 'elapsed_since_last', elapsed_since_last: effectiveSource === 'elapsed_since_last',
}) })
setName('') resetForm()
setRegex('')
setCaptureGroup('')
setJsonFieldPath('')
setSource('regex')
setKind(EMPTY_KIND)
onDeclared() onDeclared()
} catch (err) { } catch (err) {
setSubmitError(err instanceof ApiError ? err.message : 'Failed to declare param') setSubmitError(err instanceof ApiError ? err.message : `Failed to ${editingName ? 'update' : 'declare'} param`)
} finally { } finally {
setSubmitting(false) setSubmitting(false)
} }
@@ -115,13 +139,16 @@ export function Params({ params, onDeclared }: { params: ParamDeclaration[]; onD
<div className="flex gap-4 p-3" hidden={collapsed}> <div className="flex gap-4 p-3" hidden={collapsed}>
<form onSubmit={handleSubmit} className="w-80 shrink-0 space-y-2"> <form onSubmit={handleSubmit} className="w-80 shrink-0 space-y-2">
<div> <div>
<label className="mb-1 block text-xs text-text-subtle">Name</label> <label className="mb-1 block text-xs text-text-subtle">
Name{editingName && ' (locked while editing)'}
</label>
<input <input
className="w-full rounded border border-border-soft bg-surface-1 px-2 py-1 text-sm text-text placeholder:text-text-subtle focus:border-border-strong focus:outline-none" className="w-full rounded border border-border-soft bg-surface-1 px-2 py-1 text-sm text-text placeholder:text-text-subtle focus:border-border-strong focus:outline-none disabled:opacity-50"
value={name} value={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
placeholder="path" placeholder="path"
required required
disabled={editingName !== null}
/> />
</div> </div>
{!isElapsedSinceLast && ( {!isElapsedSinceLast && (
@@ -207,14 +234,31 @@ export function Params({ params, onDeclared }: { params: ParamDeclaration[]; onD
</select> </select>
</div> </div>
)} )}
{editingName && (
<div className="rounded border border-accent bg-accent/5 px-2 py-1 text-xs text-accent-text">
Editing "{editingName}"
</div>
)}
{submitError && <div className="rounded border border-danger-border bg-danger-bg/50 px-2 py-1 text-xs text-danger-text">{submitError}</div>} {submitError && <div className="rounded border border-danger-border bg-danger-bg/50 px-2 py-1 text-xs text-danger-text">{submitError}</div>}
<button <div className="flex items-center gap-2">
type="submit" <button
disabled={submitting} type="submit"
className="rounded border border-border-soft px-3 py-1 text-sm text-text-secondary hover:bg-surface-2 disabled:opacity-50" disabled={submitting}
> className="rounded border border-border-soft px-3 py-1 text-sm text-text-secondary hover:bg-surface-2 disabled:opacity-50"
{submitting ? 'Declaring…' : 'Declare param'} >
</button> {submitting ? (editingName ? 'Saving…' : 'Declaring…') : editingName ? 'Save changes' : 'Declare param'}
</button>
{editingName && (
<button
type="button"
onClick={resetForm}
disabled={submitting}
className="rounded border border-border-soft px-3 py-1 text-sm text-text-secondary hover:bg-surface-2 disabled:opacity-50"
>
Cancel
</button>
)}
</div>
</form> </form>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
@@ -222,9 +266,15 @@ export function Params({ params, onDeclared }: { params: ParamDeclaration[]; onD
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
{params.length === 0 && <span className="text-xs text-text-faint">No params declared yet.</span>} {params.length === 0 && <span className="text-xs text-text-faint">No params declared yet.</span>}
{params.map((p) => ( {params.map((p) => (
<span <button
key={p.name} key={p.name}
className="rounded border border-border-soft px-2 py-1 text-xs text-text-secondary" type="button"
onClick={() => editParam(p)}
className={`rounded border px-2 py-1 text-xs ${
editingName === p.name
? 'border-accent bg-accent/10 text-accent-text'
: 'border-border-soft text-text-secondary hover:bg-surface-2'
}`}
title={p.elapsed_since_last ? undefined : p.message_match_regex} title={p.elapsed_since_last ? undefined : p.message_match_regex}
> >
{p.name} ({p.kind} {p.name} ({p.kind}
@@ -234,7 +284,7 @@ export function Params({ params, onDeclared }: { params: ParamDeclaration[]; onD
? '' ? ''
: `, ${p.json_field_path ? `json field ${p.json_field_path}` : `group ${p.capture_group}`}`} : `, ${p.json_field_path ? `json field ${p.json_field_path}` : `group ${p.capture_group}`}`}
) )
</span> </button>
))} ))}
</div> </div>
</div> </div>