From f95b4c5b6ecbf3f1729f360ad182a19c37430172 Mon Sep 17 00:00:00 2001 From: forthfate Date: Mon, 14 Sep 2026 18:12:30 +0900 Subject: [PATCH 01/14] feat: title-case workflow phase labels --- frontend/src/features/evaluations/page.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/src/features/evaluations/page.tsx b/frontend/src/features/evaluations/page.tsx index 0ebe158..62145b7 100644 --- a/frontend/src/features/evaluations/page.tsx +++ b/frontend/src/features/evaluations/page.tsx @@ -44,6 +44,8 @@ const phases = [ "after_each", "after_all", ] as const; +const phaseLabel = (phase: string) => + phase.replaceAll("_", " ").replace(/\b\w/g, (letter) => letter.toUpperCase()); const time = (locale: Locale, value?: string) => value ? new Intl.DateTimeFormat(intlLocales[locale], { @@ -287,7 +289,9 @@ export function EvaluationsPage({ if (run.status === "succeeded") return l.complete; if (run.status === "cancelled") return l.cancelled; if (run.status === "failed") return l.failed; - return run.current_phase === "waiting" ? l.waiting : (run.current_phase ?? "—"); + return run.current_phase === "waiting" + ? l.waiting + : (run.current_phase ? phaseLabel(run.current_phase) : "—"); }; const builds = useMemo( () => [ @@ -888,7 +892,7 @@ export function EvaluationsPage({ {availablePhases.map((phase) => ( ))} @@ -1036,7 +1040,7 @@ export function EvaluationsPage({ className={phaseTab === phase ? "active" : ""} onClick={() => setPhaseTab(phase)} > - {phase} + {phaseLabel(phase)} ))} From 833c29d8b98f85c8800bd64478155fb450d0f074 Mon Sep 17 00:00:00 2001 From: forthfate Date: Mon, 14 Sep 2026 18:31:20 +0900 Subject: [PATCH 02/14] feat: refine run detail observability panels --- frontend/src/features/evaluations/page.tsx | 5 ++- .../evaluations/run-detail-log-panels.tsx | 7 ++-- .../evaluations/run-detail-result-panel.tsx | 4 +-- .../run-detail-supervisor-panel.tsx | 34 +++---------------- .../evaluations/run-detail-telemetry-tree.tsx | 28 +++++++++++++++ frontend/src/theme-overrides.css | 5 ++- 6 files changed, 46 insertions(+), 37 deletions(-) create mode 100644 frontend/src/features/evaluations/run-detail-telemetry-tree.tsx diff --git a/frontend/src/features/evaluations/page.tsx b/frontend/src/features/evaluations/page.tsx index 62145b7..2a71533 100644 --- a/frontend/src/features/evaluations/page.tsx +++ b/frontend/src/features/evaluations/page.tsx @@ -1061,6 +1061,10 @@ export function EvaluationsPage({ empty={l.noLogs} orbitLogs={l.orbitLogs} targetLogs={l.targetLogs} + telemetry={telemetry} + openTelemetryTrace={l.openTelemetryTrace} + loadingOpenTelemetryTrace={l.loadingOpenTelemetryTrace} + noOpenTelemetrySpans={l.noOpenTelemetrySpans} /> )} @@ -1106,7 +1110,6 @@ export function EvaluationsPage({ } /> diff --git a/frontend/src/features/evaluations/run-detail-log-panels.tsx b/frontend/src/features/evaluations/run-detail-log-panels.tsx index 7758100..3a6d9f1 100644 --- a/frontend/src/features/evaluations/run-detail-log-panels.tsx +++ b/frontend/src/features/evaluations/run-detail-log-panels.tsx @@ -1,5 +1,6 @@ -import type { RunStepResult } from "../../domain/models"; +import type { RunStepResult, RunTelemetry } from "../../domain/models"; import { intlLocales, type Locale } from "../../locales"; +import { TelemetryTree } from "./run-detail-telemetry-tree"; const time = (locale: Locale, value?: string) => value ? new Intl.DateTimeFormat(intlLocales[locale], { dateStyle: "medium", timeStyle: "medium" }).format(new Date(value)) : "—"; const stepLogLines = (step: RunStepResult) => step.log_lines?.length ? step.log_lines.map(({ timestamp, value }) => ({ value, timestamp })) : (step.output ?? step.error ?? "—").split("\n").map((value) => ({ value, timestamp: step.ended_at ?? step.started_at })); @@ -18,11 +19,11 @@ export function WorkflowLogPanel({ steps, locale, empty }: { steps: RunStepResul return
{visibleSteps.length ? visibleSteps.map((step, index) =>
{step.result && }{(step.log_lines?.length || step.output || step.error) && }
) :

{empty}

}
; } -export function CombinedLogPanel({ steps, locale, empty, orbitLogs, targetLogs }: { steps: RunStepResult[]; locale: Locale; empty: string; orbitLogs: string; targetLogs: string }) { +export function CombinedLogPanel({ steps, locale, empty, orbitLogs, targetLogs, telemetry, openTelemetryTrace, loadingOpenTelemetryTrace, noOpenTelemetrySpans }: { steps: RunStepResult[]; locale: Locale; empty: string; orbitLogs: string; targetLogs: string; telemetry?: RunTelemetry; openTelemetryTrace?: string; loadingOpenTelemetryTrace?: string; noOpenTelemetrySpans?: string }) { const logSteps = steps.filter((step) => step.output || step.error); const targetLines = steps.flatMap((step) => step.target_logs ?? []).map((entry) => ({ timestamp: entry.timestamp, value: `[${entry.level ?? "info"}]${entry.source ? ` ${entry.source}` : ""} ${entry.message}`, })); - return

{orbitLogs}

{logSteps.length ? :

{empty}

}

{targetLogs}

{targetLines.length ? :

{empty}

}
; + return

{orbitLogs}

{logSteps.length ? :

{empty}

}

{targetLogs}

{targetLines.length ? :

{empty}

}

{openTelemetryTrace}

; } diff --git a/frontend/src/features/evaluations/run-detail-result-panel.tsx b/frontend/src/features/evaluations/run-detail-result-panel.tsx index 41596c2..7fd8695 100644 --- a/frontend/src/features/evaluations/run-detail-result-panel.tsx +++ b/frontend/src/features/evaluations/run-detail-result-panel.tsx @@ -13,7 +13,7 @@ type BehaviorTrace = { const time = (locale: Locale, value?: string) => value ? new Intl.DateTimeFormat(intlLocales[locale], { dateStyle: "medium", timeStyle: "medium" }).format(new Date(value)) : "—"; function ResultList({ items, kind, locale, empty }: { items: RecordItem[]; kind: "improvement" | "issue"; locale: Locale; empty: string }) { - return
{items.length ? items.map((item, index) => { + return
{items.length ? items.map((item, index) => { const status = String(item.status ?? "—"), severity = String(item.severity ?? "—"), reportedAt = typeof item.reported_at === "string" ? item.reported_at : undefined; return
{String(item.title ?? "—")}

{String(kind === "improvement" ? (item.rationale ?? "—") : (item.evidence ?? "—"))}

Iteration#{String(item.__iteration ?? "—")}{kind === "improvement" ? <>Status{status}Score{String(item.effect_score ?? item.score ?? "—")}Attempted{item.attempted === true || status === "adopted" ? "Yes" : "No"} : <>Severity{severity}Status{status}}
; }) :

{empty}

}
; @@ -49,5 +49,5 @@ export function EvaluationResultPanel({ error, records, summaries, improvements, ]; return fields.filter((field): field is [string | undefined, string] => Boolean(field[1])); }; - return <>{error}{records.length ? <>{summaries.length > 0 &&

{l.observedBehavior}

{summaries.map((item) =>
{traceFields(item).length ? <>
{traceFields(item).map(([label, value]) =>
{label}
{value}
)}
{evidenceIteration === item.iteration && item.trace?.evidence &&
{l.traceEvidence}

{item.trace.evidence}

} :

{item.summary}

}
Iteration#{item.iteration}{item.trace?.evidence && }
)}
}

{l.proposals}

{l.issues}

:

{l.noMatchingResults}

}; + return <>{error}{records.length ? <>{summaries.length > 0 &&

{l.observedBehavior}

{summaries.map((item) =>
{traceFields(item).length ? <>
{traceFields(item).map(([label, value]) =>
{label}
{value}
)}
{evidenceIteration === item.iteration && item.trace?.evidence &&
{l.traceEvidence}

{item.trace.evidence}

} :

{item.summary}

}
Iteration#{item.iteration}{item.trace?.evidence && }
)}
}

{l.proposals}

{l.issues}

:

{l.noMatchingResults}

}; } diff --git a/frontend/src/features/evaluations/run-detail-supervisor-panel.tsx b/frontend/src/features/evaluations/run-detail-supervisor-panel.tsx index 6ec4864..79b2222 100644 --- a/frontend/src/features/evaluations/run-detail-supervisor-panel.tsx +++ b/frontend/src/features/evaluations/run-detail-supervisor-panel.tsx @@ -1,36 +1,10 @@ -import { useMemo, useState, type ReactNode } from "react"; -import type { RunTelemetry, SupervisorRecord, TelemetrySpan } from "../../domain/models"; +import type { ReactNode } from "react"; +import type { SupervisorRecord } from "../../domain/models"; import { StatusBadge } from "../../components/ui/status-badge"; type Messages = Record; -function telemetryValue(value: unknown) { - return typeof value === "string" ? value : JSON.stringify(value); -} - -function duration(span: TelemetrySpan) { - if (!span.startTime || !span.endTime || span.endTime < span.startTime) return undefined; - return `${((span.endTime - span.startTime) / 1_000_000).toFixed(1)} ms`; -} - -function TelemetryTree({ telemetry, l }: { telemetry?: RunTelemetry; l: Messages }) { - const [collapsed, setCollapsed] = useState>(new Set()); - const tree = useMemo(() => { - const spans = telemetry?.spans ?? [], children = new Map(), known = new Set(spans.map((span) => span.spanId)); - for (const span of spans) if (span.parentSpanId && known.has(span.parentSpanId)) children.set(span.parentSpanId, [...(children.get(span.parentSpanId) ?? []), span]); - return { roots: spans.filter((span) => !span.parentSpanId || !known.has(span.parentSpanId)), children }; - }, [telemetry]); - if (!telemetry) return

{l.loadingOpenTelemetryTrace}

; - if (!tree.roots.length) return

{l.noOpenTelemetrySpans}

; - const render = (span: TelemetrySpan): ReactNode => { - const children = tree.children.get(span.spanId) ?? [], expandable = children.length > 0, isCollapsed = collapsed.has(span.spanId), attributes = Object.entries(span.attributes ?? {}), events = span.events ?? []; - const summary = [span.status || "UNSET", duration(span), ...events.map((event) => event.name)].filter(Boolean).join(" · "); - return
  • {!isCollapsed && (attributes.length > 0 || events.some((event) => Object.keys(event.attributes ?? {}).length > 0)) &&
    {attributes.map(([key, value]) =>
    {key}
    {telemetryValue(value)}
    )}{events.flatMap((event) => Object.entries(event.attributes ?? {}).map(([key, value]) =>
    {event.name} · {key}
    {telemetryValue(value)}
    ))}
    }{expandable && !isCollapsed &&
      {children.map(render)}
    }
  • ; - }; - return
      {tree.roots.map(render)}
    ; -} - -export function SupervisorPanel({ record, l, telemetry, renderLineOutput }: { record?: SupervisorRecord; l: Messages; telemetry?: RunTelemetry; renderLineOutput: (value: string) => ReactNode }) { +export function SupervisorPanel({ record, l, renderLineOutput }: { record?: SupervisorRecord; l: Messages; renderLineOutput: (value: string) => ReactNode }) { const response = record?.response; - return
    {l.supervisorPrompt}
    {renderLineOutput(record?.prompt || l.noSupervisorPrompt || "")}
    {l.supervisorResponse}{response ? renderLineOutput(JSON.stringify(response, null, 2)) :

    {record?.error || l.supervisorWaiting}

    }
    {l.openTelemetryTrace}
    ; + return
    {l.supervisorPrompt}
    {renderLineOutput(record?.prompt || l.noSupervisorPrompt || "")}
    {l.supervisorResponse}{response ? renderLineOutput(JSON.stringify(response, null, 2)) :

    {record?.error || l.supervisorWaiting}

    }
    ; } diff --git a/frontend/src/features/evaluations/run-detail-telemetry-tree.tsx b/frontend/src/features/evaluations/run-detail-telemetry-tree.tsx new file mode 100644 index 0000000..58a8c09 --- /dev/null +++ b/frontend/src/features/evaluations/run-detail-telemetry-tree.tsx @@ -0,0 +1,28 @@ +import { useMemo, useState, type ReactNode } from "react"; +import type { RunTelemetry, TelemetrySpan } from "../../domain/models"; + +function telemetryValue(value: unknown) { + return typeof value === "string" ? value : JSON.stringify(value); +} + +function duration(span: TelemetrySpan) { + if (!span.startTime || !span.endTime || span.endTime < span.startTime) return undefined; + return `${((span.endTime - span.startTime) / 1_000_000).toFixed(1)} ms`; +} + +export function TelemetryTree({ telemetry, loading, empty }: { telemetry?: RunTelemetry; loading?: string; empty?: string }) { + const [collapsed, setCollapsed] = useState>(new Set()); + const tree = useMemo(() => { + const spans = telemetry?.spans ?? [], children = new Map(), known = new Set(spans.map((span) => span.spanId)); + for (const span of spans) if (span.parentSpanId && known.has(span.parentSpanId)) children.set(span.parentSpanId, [...(children.get(span.parentSpanId) ?? []), span]); + return { roots: spans.filter((span) => !span.parentSpanId || !known.has(span.parentSpanId)), children }; + }, [telemetry]); + if (!telemetry) return

    {loading}

    ; + if (!tree.roots.length) return

    {empty}

    ; + const render = (span: TelemetrySpan): ReactNode => { + const children = tree.children.get(span.spanId) ?? [], expandable = children.length > 0, isCollapsed = collapsed.has(span.spanId), attributes = Object.entries(span.attributes ?? {}), events = span.events ?? []; + const summary = [span.status || "UNSET", duration(span), ...events.map((event) => event.name)].filter(Boolean).join(" · "); + return
  • {!isCollapsed && (attributes.length > 0 || events.some((event) => Object.keys(event.attributes ?? {}).length > 0)) &&
    {attributes.map(([key, value]) =>
    {key}
    {telemetryValue(value)}
    )}{events.flatMap((event) => Object.entries(event.attributes ?? {}).map(([key, value]) =>
    {event.name} · {key}
    {telemetryValue(value)}
    ))}
    }{expandable && !isCollapsed &&
      {children.map(render)}
    }
  • ; + }; + return
      {tree.roots.map(render)}
    ; +} diff --git a/frontend/src/theme-overrides.css b/frontend/src/theme-overrides.css index 5b4ee04..3d7cb20 100644 --- a/frontend/src/theme-overrides.css +++ b/frontend/src/theme-overrides.css @@ -88,7 +88,7 @@ footer { display:flex; align-items:center; justify-content:space-between; margin .chat-tools-popover { position:fixed; z-index:40; display:grid; width:296px; gap:10px; padding:14px; border:1px solid var(--line); border-radius:10px; color:var(--text); background:var(--surface); box-shadow:0 16px 42px rgb(0 0 0 / .35); font-size:12px; }.chat-tools-popover > strong { font-size:13px; }.chat-tools-popover > p { margin:0; padding:7px 9px; border-radius:6px; color:var(--muted); background:var(--surface-raised); font-size:11px; }.chat-tools-popover > label { display:grid; gap:5px; color:var(--muted); }.chat-tools-popover input:not([type="checkbox"]) { width:100%; box-sizing:border-box; font:inherit; }.chat-tools-popover .chat-tool-popover-toggle { display:flex; align-items:center; justify-content:space-between; padding:8px 0; border-bottom:1px solid var(--line); color:var(--text); }.chat-tools-popover > div { display:flex; justify-content:flex-end; gap:8px; padding-top:2px; } .chat-assistant { resize:both; min-width:320px; min-height:360px; max-width:calc(100vw - 24px); max-height:calc(100vh - 24px); }.chat-pane { display:grid; grid-row:2 / 5; grid-template-rows:auto minmax(0,1fr) auto; min-height:0; }.chat-pane--hidden { display:none; }.assistant-terminal { display:none; }.assistant-terminal--active { display:block; } .chat-message--assistant > :first-child { margin-top:0; }.chat-message--assistant > :last-child { margin-bottom:0; }.chat-message--assistant h1,.chat-message--assistant h2,.chat-message--assistant h3 { margin:12px 0 7px; font-size:13px; }.chat-message--assistant p,.chat-message--assistant ul,.chat-message--assistant ol { margin:8px 0; }.chat-message--assistant ul,.chat-message--assistant ol { padding-left:18px; }.chat-message--assistant a { color:var(--accent); }.chat-message--assistant pre { overflow:auto; margin:9px 0; padding:9px; border-radius:6px; background:var(--bg); font:10px/1.55 'DM Mono',monospace; }.chat-message--assistant code { font:10px 'DM Mono',monospace; }.chat-message--assistant :not(pre) > code { padding:2px 4px; border-radius:4px; background:var(--bg); }.chat-message--assistant table { display:block; max-width:100%; overflow:auto; border-collapse:collapse; }.chat-message--assistant th,.chat-message--assistant td { padding:5px 7px; border:1px solid var(--line); text-align:left; } -.run-detail-trigger { border:0; padding:0; color:inherit; background:transparent; text-align:left; cursor:pointer; }.run-detail { position:fixed; z-index:15; top:0; right:0; width:min(560px,100vw); height:100vh; overflow:auto; padding:24px; border-left:1px solid var(--line); background:var(--surface); box-shadow:-16px 0 48px rgb(0 0 0 / .35); }.run-detail header { margin-bottom:18px; }.run-tabs { display:flex; gap:6px; border-bottom:1px solid var(--line); }.run-tabs button { border:0; border-bottom:2px solid transparent; padding:10px 12px; color:var(--muted); background:transparent; }.run-tabs button.active { border-color:var(--accent); color:var(--text); }.console-output section { padding:14px 0; border-bottom:1px solid var(--line); }.console-output small { display:block; color:var(--muted); margin:4px 0; }.line-numbered-output { max-width:100%; overflow:auto; padding:8px 0; border:1px solid var(--line); border-radius:8px; background:var(--bg); color:var(--text); font:11px/1.55 'DM Mono',monospace; }.line-numbered-output > div { display:grid; grid-template-columns:46px minmax(0,1fr); min-width:max-content; }.line-numbered-output span { padding:0 10px; border-right:1px solid var(--line); color:var(--muted); text-align:right; user-select:none; }.line-numbered-output code { min-width:0; padding:0 12px; color:inherit; white-space:pre; }.supervisor-translation-action { display:flex; justify-content:flex-end; margin-bottom:10px; }.supervisor-output { display:grid; gap:16px; max-height:min(510px,calc(100vh - 310px)); overflow:auto; padding:18px 8px 0 0; }.supervisor-output section { display:grid; gap:8px; }.supervisor-output__head { display:flex; align-items:center; justify-content:space-between; gap:12px; }.supervisor-output p { margin:0; color:var(--muted); } +.run-detail-trigger { border:0; padding:0; color:inherit; background:transparent; text-align:left; cursor:pointer; }.run-detail { position:fixed; z-index:15; top:0; right:0; width:min(560px,100vw); height:100vh; overflow:auto; padding:24px; border-left:1px solid var(--line); background:var(--surface); box-shadow:-16px 0 48px rgb(0 0 0 / .35); }.run-detail header { margin-bottom:18px; }.run-tabs { display:flex; gap:6px; border-bottom:1px solid var(--line); }.run-tabs button { border:0; border-bottom:2px solid transparent; padding:10px 12px; color:var(--muted); background:transparent; }.run-tabs button.active { border-color:var(--accent); color:var(--text); }.console-output section { padding:14px 0; border-bottom:1px solid var(--line); }.console-output small { display:block; color:var(--muted); margin:4px 0; }.line-numbered-output { max-width:100%; overflow:auto; padding:8px 0; border:1px solid var(--line); border-radius:8px; background:var(--bg); color:var(--text); font:11px/1.55 'DM Mono',monospace; }.line-numbered-output > div { display:grid; grid-template-columns:46px minmax(0,1fr); min-width:max-content; }.line-numbered-output span { padding:0 10px; border-right:1px solid var(--line); color:var(--muted); text-align:right; user-select:none; }.line-numbered-output code { min-width:0; padding:0 12px; color:inherit; white-space:pre; }.supervisor-translation-action { display:flex; justify-content:flex-end; margin-bottom:10px; }.supervisor-output { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px; padding:0; }.supervisor-output section { display:grid; grid-template-rows:auto minmax(0,1fr); gap:8px; height:clamp(260px,calc(100vh - 390px),460px); min-height:0; padding:12px; border:1px solid var(--line); border-radius:8px; background:var(--surface-raised); }.supervisor-output .line-numbered-output { min-height:0; }.supervisor-output__head { display:flex; align-items:center; justify-content:space-between; gap:12px; }.supervisor-output p { margin:0; overflow:auto; color:var(--muted); } @media(max-width:760px){.supervisor-output { grid-template-columns:1fr; }.supervisor-output section { height:300px; }} .iteration-navigator { display:flex; align-items:center; justify-content:flex-end; gap:6px; min-height:48px; border-bottom:1px solid var(--line); }.iteration-navigator select { width:auto; min-width:76px; height:30px; padding:0 28px 0 10px; font:11px 'DM Mono',monospace; }.iteration-navigator .icon-button { display:grid; width:30px; height:30px; place-items:center; padding:0; }.iteration-navigator .icon-button:disabled { cursor:not-allowed; opacity:.4; } .app-settings { display:grid; grid-template-columns:1fr; gap:0; }.app-settings + .app-settings { margin-top:16px; }.setting-row { display:flex !important; align-items:center; justify-content:space-between; gap:24px; padding:18px 0; border-top:1px solid var(--line); }.setting-row span { display:grid; gap:5px; }.setting-row strong { color:var(--text); }.setting-row select { min-width:220px; }.setting-actions { display:flex; align-items:center; gap:8px; }.setting-actions .approve { white-space:nowrap; } @media (max-width:900px) { .build-form,.profile-form { grid-template-columns:1fr; } } @@ -133,6 +133,9 @@ footer { display:flex; align-items:center; justify-content:space-between; margin select option[value="docker"] { display:none; } .sidebar-toggle { position:absolute; top:35px; right:20px; z-index:1; display:grid; width:30px; height:30px; padding:0; place-items:center; border:0; border-radius:8px; color:var(--muted); background:transparent; transition:color .15s ease,background .15s ease; }.sidebar-toggle:hover,.sidebar-toggle:focus-visible { color:var(--text); background:var(--surface-raised); outline:0; } main > aside,main > .content { transition:width .2s ease,margin-left .2s ease,padding .2s ease; } main.sidebar-collapsed > aside { width:76px; padding-inline:12px; } main.sidebar-collapsed > .content { width:calc(100% - 76px); margin-left:76px; } main.sidebar-collapsed .sidebar-toggle { display:none; } main.sidebar-collapsed .brand { justify-content:center; margin-inline:0; } .brand--expand { border:0; padding:0; background:transparent; cursor:pointer; } .brand--expand:hover,.brand--expand:focus-visible { color:var(--text); outline:0; } main.sidebar-collapsed .brand > div,main.sidebar-collapsed .nav-label { display:none; } main.sidebar-collapsed nav button { position:relative; justify-content:center; padding-inline:0; } main.sidebar-collapsed .nav-run-count { position:absolute; top:3px; right:3px; min-width:16px; height:16px; padding-inline:3px; font-size:8px; } +.run-result { max-height:none; overflow:visible; padding:18px 0 0; }.run-result h3 { margin:0; }.result-panels { display:grid; grid-template-columns:repeat(auto-fit,minmax(280px,1fr)); gap:12px; }.result-panel { display:grid; grid-template-rows:auto minmax(0,1fr); gap:8px; height:clamp(260px,calc(100vh - 390px),460px); min-height:0; padding:12px; border:1px solid var(--line); border-radius:8px; background:var(--surface-raised); }.result-panel .result-items { min-height:0; overflow:auto; padding-right:4px; }.combined-log-output { display:grid; grid-template-columns:1fr; grid-template-rows:repeat(3,minmax(0,1fr)); gap:0; height:min(510px,calc(100vh - 310px)); max-height:none; overflow:visible; padding-right:8px; }.combined-log-output section { display:grid; grid-template-rows:auto minmax(0,1fr); gap:8px; min-height:0; height:auto; padding:14px 0; border:0; border-bottom:1px solid var(--line); border-radius:0; background:transparent; }.combined-log-output section:last-child { border-bottom:0; }.combined-log-output section h3 { margin:0 0 10px; font-size:13px; }.combined-log-output .timestamped-log-output,.combined-log-output .telemetry-tree { min-height:0; overflow:auto; }.combined-log-output .hint { margin:0; overflow:auto; }.supervisor-output { grid-template-columns:1fr; grid-template-rows:repeat(2,minmax(0,1fr)); gap:16px; height:min(510px,calc(100vh - 310px)); padding:18px 8px 0 0; overflow:visible; }.supervisor-output section { height:auto; padding:0; border:0; border-radius:0; background:transparent; } @media(max-width:720px){.result-panels { grid-template-columns:1fr; }.result-panel { height:300px; }.combined-log-output { height:min(510px,calc(100vh - 310px)); }.combined-log-output section { height:auto; }.supervisor-output { height:min(510px,calc(100vh - 310px)); }.supervisor-output section { height:auto; }} +.run-result { max-height:none; overflow:visible; padding:18px 8px 0 0; }.run-result h3 { margin:0 0 10px; }.result-items--scrollable { max-height:150px; overflow:auto; padding-right:4px; }.combined-log-output { display:block; height:auto; max-height:none; overflow:visible; padding-right:8px; }.combined-log-output section { display:block; min-height:0; height:auto; padding:14px 0; border:0; border-bottom:1px solid var(--line); border-radius:0; background:transparent; }.combined-log-output section:last-child { border-bottom:0; }.combined-log-output section h3 { margin:0 0 10px; font-size:13px; }.combined-log-output .timestamped-log-output,.combined-log-output .telemetry-tree { max-height:160px; min-height:0; overflow:auto; }.combined-log-output .hint { margin:0; }.supervisor-output { grid-template-columns:1fr; grid-template-rows:repeat(2,minmax(0,1fr)); gap:16px; height:min(510px,calc(100vh - 310px)); padding:18px 8px 0 0; overflow:visible; }.supervisor-output section { height:auto; padding:0; border:0; border-radius:0; background:transparent; } +.result-items--scrollable { max-height:225px; }.combined-log-output .timestamped-log-output,.combined-log-output .telemetry-tree { max-height:240px; }.supervisor-output { height:min(765px,calc(100vh - 310px)); } @media (prefers-reduced-motion:reduce) { main > aside,main > .content { transition:none; } } .runner-template-picker { display:grid; gap:14px; }.runner-template-picker__head { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; }.runner-template-picker__head .hint { margin:0; }.template-picker-actions { display:flex; flex-wrap:wrap; justify-content:flex-end; gap:8px; }.visually-hidden { position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; }.runner-template-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px; }.runner-template-card { display:grid; gap:9px; min-height:160px; padding:16px; border:1px solid var(--line); border-radius:9px; color:var(--text); text-align:left; background:var(--bg); cursor:pointer; }.runner-template-card:hover { border-color:var(--accent); background:var(--surface-raised); }.runner-template-card span,.runner-template-card small { color:var(--muted); font-size:11px; line-height:1.55; }.runner-template-selection { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; padding:12px; border:1px solid color-mix(in srgb,var(--accent) 50%,var(--line)); border-radius:8px; background:color-mix(in srgb,var(--accent) 8%,var(--bg)); }.runner-template-selection > div { display:grid; gap:4px; min-width:0; }.runner-template-selection small,.runner-template-selection span { color:var(--muted); font-size:11px; }.runner-template-selection strong { font-size:13px; }.runner-template-selection span { line-height:1.45; }.runner-template-selection button { flex:none; }.runner-source { display:grid; gap:8px; color:var(--text); font-size:12px; }.runner-source textarea { min-height:440px; resize:vertical; font:12px/1.55 'DM Mono',monospace; tab-size:2; } .runner-template-card small { display:none; } From 6ec6a4fa6cbb7e886cbef1e4b8ea7421f4cc4555 Mon Sep 17 00:00:00 2001 From: forthfate Date: Mon, 14 Sep 2026 18:39:21 +0900 Subject: [PATCH 03/14] fix: localize run and asset notifications --- frontend/src/features/assets/page.tsx | 4 ++-- frontend/src/features/builds/page.tsx | 6 +++--- frontend/src/features/settings/page.tsx | 2 +- frontend/src/locales/index.ts | 9 ++++++++- frontend/src/locales/languages/en.json | 12 +++++++++++- frontend/src/locales/languages/ja.json | 12 +++++++++++- frontend/src/locales/languages/ko.json | 12 +++++++++++- frontend/src/main.tsx | 6 +++--- 8 files changed, 50 insertions(+), 13 deletions(-) diff --git a/frontend/src/features/assets/page.tsx b/frontend/src/features/assets/page.tsx index bd44204..274a4e6 100644 --- a/frontend/src/features/assets/page.tsx +++ b/frontend/src/features/assets/page.tsx @@ -1639,9 +1639,9 @@ function EnvironmentCatalog({ } setKind(null); await onRefresh(); - pushToast("Asset saved", "success"); + pushToast(t.assetSaved, "success"); } catch (error) { - pushToast(error instanceof Error ? error.message : "Asset save failed"); + pushToast(error instanceof Error ? error.message : t.assetSaveFailed); } }; const help = fieldHelp[locale], sectionDetails = localeMessages>(locale, "sectionDetails"); diff --git a/frontend/src/features/builds/page.tsx b/frontend/src/features/builds/page.tsx index 0155ea9..3d59262 100644 --- a/frontend/src/features/builds/page.tsx +++ b/frontend/src/features/builds/page.tsx @@ -642,7 +642,7 @@ function Quick({ await create(item.id, v); close(); } catch (e) { - setError(e instanceof Error ? e.message : "Creation failed"); + setError(e instanceof Error ? e.message : t.creationFailed); } finally { setBusy(false); } @@ -844,7 +844,7 @@ export function BuildsPage(props: { .then(setTestRun) .catch((error) => setError( - error instanceof Error ? error.message : "Test failed to start", + error instanceof Error ? error.message : ui.testFailedToStart, ), ); }; @@ -891,7 +891,7 @@ export function BuildsPage(props: { }); setItems(await api("/api/quick-starts")); } catch (e) { - setError(e instanceof Error ? e.message : "Import failed"); + setError(e instanceof Error ? e.message : ui.importFailed); } }; const cols: Column[] = [ diff --git a/frontend/src/features/settings/page.tsx b/frontend/src/features/settings/page.tsx index 6f01ace..9017b4d 100644 --- a/frontend/src/features/settings/page.tsx +++ b/frontend/src/features/settings/page.tsx @@ -76,7 +76,7 @@ export function SettingsPage({ setPrompt(values.manager_prompt_template); setChatProfile(values.chat_model_profile_name); }) - .catch(() => pushToast("Unable to load operational prompt.")); + .catch(() => pushToast(locales[locale].ui.operationalPromptLoadFailed)); }, [locale, pushToast]); useEffect(() => { let mounted = true; diff --git a/frontend/src/locales/index.ts b/frontend/src/locales/index.ts index d464495..5dcc227 100644 --- a/frontend/src/locales/index.ts +++ b/frontend/src/locales/index.ts @@ -14,7 +14,14 @@ const interpolate = (template: string, value: number) => template.replace(/\{(?: // eslint-disable-next-line @typescript-eslint/no-explicit-any const hydrateMessages = (messages: Record) => ({ ...messages, - ui: { ...messages.ui, itemsPerPageValue: (size: number) => interpolate(messages.ui.itemsPerPageValue, size) }, + ui: { + ...messages.ui, + itemsPerPageValue: (size: number) => interpolate(messages.ui.itemsPerPageValue, size), + runsDeleted: (count: number) => + count === 1 ? messages.ui.runDeleted : interpolate(messages.ui.runsDeleted, count), + settingsTestSucceeded: (response: string) => + messages.ui.settingsTestSucceeded.replace("{response}", response), + }, runUi: { ...messages.runUi, selected: (count: number) => interpolate(messages.runUi.selected, count), diff --git a/frontend/src/locales/languages/en.json b/frontend/src/locales/languages/en.json index b817ab0..65ed231 100644 --- a/frontend/src/locales/languages/en.json +++ b/frontend/src/locales/languages/en.json @@ -445,6 +445,14 @@ "buildDeleted": "Build deleted", "quickStartCreated": "Quick Start created its assets and a build", "assetDeleted": "Asset deleted", + "runDeleted": "Run deleted", + "runsDeleted": "{count} runs deleted", + "settingsTestSucceeded": "Test succeeded: {response}", + "evaluationRetryStarted": "Evaluation retry started.", + "creationFailed": "Creation failed", + "testFailedToStart": "Test failed to start", + "importFailed": "Import failed", + "operationalPromptLoadFailed": "Unable to load operational prompt.", "apiConnectionFailed": "API connection failed." }, "runUi": { @@ -540,7 +548,9 @@ "library": "Browser library path (optional)", "repository": "Repository", "baseUrl": "Browser base URL (optional)", - "promptPath": "Improvement target prompt file path (Native Improvement Cycle only)" + "promptPath": "Improvement target prompt file path (Native Improvement Cycle only)", + "assetSaved": "Asset saved", + "assetSaveFailed": "Asset save failed" }, "improvementPage": { "improvement": "Improvement", diff --git a/frontend/src/locales/languages/ja.json b/frontend/src/locales/languages/ja.json index 8fc7f1a..a7bfefd 100644 --- a/frontend/src/locales/languages/ja.json +++ b/frontend/src/locales/languages/ja.json @@ -444,6 +444,14 @@ "buildDeleted": "ビルドを削除しました", "quickStartCreated": "クイックスタートがアセットとビルドを作成しました", "assetDeleted": "アセットを削除しました", + "runDeleted": "実行を削除しました", + "runsDeleted": "{count} 件の実行を削除しました", + "settingsTestSucceeded": "テストに成功しました: {response}", + "evaluationRetryStarted": "実行の再試行を開始しました。", + "creationFailed": "作成に失敗しました", + "testFailedToStart": "テストを開始できませんでした", + "importFailed": "インポートに失敗しました", + "operationalPromptLoadFailed": "運用プロンプトを読み込めませんでした。", "apiConnectionFailed": "API 接続に失敗しました。" }, "runUi": { @@ -539,7 +547,9 @@ "library": "ブラウザーライブラリパス(任意)", "repository": "リポジトリ", "baseUrl": "ブラウザー基本 URL(任意)", - "promptPath": "改善対象プロンプトファイルパス(Native Improvement Cycle 専用)" + "promptPath": "改善対象プロンプトファイルパス(Native Improvement Cycle 専用)", + "assetSaved": "アセットを保存しました", + "assetSaveFailed": "アセットを保存できませんでした" }, "improvementPage": { "improvement": "改善", diff --git a/frontend/src/locales/languages/ko.json b/frontend/src/locales/languages/ko.json index c10b73c..43243c3 100644 --- a/frontend/src/locales/languages/ko.json +++ b/frontend/src/locales/languages/ko.json @@ -444,6 +444,14 @@ "buildDeleted": "빌드를 삭제했습니다", "quickStartCreated": "퀵 스타트가 자산과 빌드를 생성했습니다", "assetDeleted": "자산을 삭제했습니다", + "runDeleted": "실행을 삭제했습니다", + "runsDeleted": "{count}개의 실행을 삭제했습니다", + "settingsTestSucceeded": "테스트에 성공했습니다: {response}", + "evaluationRetryStarted": "실행 재시도를 시작했습니다.", + "creationFailed": "생성에 실패했습니다", + "testFailedToStart": "테스트를 시작하지 못했습니다", + "importFailed": "가져오기에 실패했습니다", + "operationalPromptLoadFailed": "운영 프롬프트를 불러오지 못했습니다.", "apiConnectionFailed": "API 연결에 실패했습니다." }, "runUi": { @@ -539,7 +547,9 @@ "library": "브라우저 라이브러리 경로 (선택)", "repository": "저장소", "baseUrl": "브라우저 기본 URL (선택)", - "promptPath": "개선 대상 프롬프트 파일 경로 (Native Improvement Cycle 전용)" + "promptPath": "개선 대상 프롬프트 파일 경로 (Native Improvement Cycle 전용)", + "assetSaved": "자산을 저장했습니다", + "assetSaveFailed": "자산을 저장하지 못했습니다" }, "improvementPage": { "improvement": "개선", diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 7167c2b..134fd22 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -108,7 +108,7 @@ export default function App() { api<{ response: string }>("/api/settings/hello", "POST", room.settings) .then((r) => { room.setSettingsTested(true); - room.setNotice(`Test succeeded: ${r.response}`, "success"); + room.setNotice(ui.settingsTestSucceeded(r.response), "success"); }) .catch((e) => room.setNotice(e.message)); const stop = () => @@ -132,7 +132,7 @@ export default function App() { const retryRun = (id: string, restartFromFirst: boolean) => api(`/api/runs/${id}/retry`, "POST", { restart_from_first: restartFromFirst }) .then(() => { - room.setNotice("Evaluation retry started.", "warning"); + room.setNotice(ui.evaluationRetryStarted, "warning"); room.refresh(); }) .catch((e) => room.setNotice(e.message)); @@ -142,7 +142,7 @@ export default function App() { ) .then(() => { room.setNotice( - `${ids.length} run${ids.length === 1 ? "" : "s"} deleted`, + ui.runsDeleted(ids.length), "success", ); return room.refresh(); From d67b11150ab7e0438f835d4ffbe1ea0b8128c59f Mon Sep 17 00:00:00 2001 From: forthfate Date: Mon, 14 Sep 2026 18:41:34 +0900 Subject: [PATCH 04/14] refactor: simplify run selection actions --- frontend/src/features/evaluations/page.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/features/evaluations/page.tsx b/frontend/src/features/evaluations/page.tsx index 2a71533..d110f9f 100644 --- a/frontend/src/features/evaluations/page.tsx +++ b/frontend/src/features/evaluations/page.tsx @@ -919,7 +919,6 @@ export function EvaluationsPage({
    {ui.selected(selectedRunIds.size)} -
    Date: Mon, 14 Sep 2026 18:44:13 +0900 Subject: [PATCH 05/14] fix: localize feedback and constrain stored state --- frontend/src/theme-overrides.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/theme-overrides.css b/frontend/src/theme-overrides.css index 3d7cb20..d58705b 100644 --- a/frontend/src/theme-overrides.css +++ b/frontend/src/theme-overrides.css @@ -55,7 +55,7 @@ footer { display:flex; align-items:center; justify-content:space-between; margin .iteration-trends__head { display:flex; align-items:center; justify-content:space-between; gap:12px; }.iteration-trends__head label { display:flex; align-items:center; gap:8px; color:var(--muted); font-size:11px; }.iteration-trends__head h3 { margin:0; }.iteration-trends__head select { width:auto; max-width:320px; } @media(max-width:720px){.iteration-trends__head { align-items:flex-start; flex-direction:column; }} .analytics-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:14px; }.analytics-chart { min-width:0; padding:14px; border:1px solid var(--line); border-radius:8px; background:var(--bg); }.analytics-chart h3 { margin:0 0 12px; font-size:12px; }.analytics-chart .recharts-cartesian-axis-tick-value,.analytics-chart .recharts-legend-item-text { fill:var(--muted)!important; font-size:10px; }.analytics-chart .recharts-tooltip-wrapper { color:#182132; }.chart-select { display:flex; justify-content:flex-end; margin:-31px 0 10px; }.chart-select label { display:flex; align-items:center; gap:8px; color:var(--muted); font-size:11px; }.chart-select select { width:auto; max-width:320px; } @media(max-width:720px){.analytics-grid { grid-template-columns:1fr; }.chart-select { justify-content:flex-start; margin:0 0 10px; }} .improvements-build-selector { display:flex; justify-content:flex-end; margin-bottom:16px; }.improvements-build-selector label { display:flex; align-items:center; gap:8px; color:var(--muted); font-size:11px; }.improvements-build-selector select { width:auto; max-width:360px; } @media(max-width:720px){.improvements-build-selector { justify-content:flex-start; }} -.stored-state { display:grid; gap:12px; margin-bottom:16px; }.stored-state .panel-head { margin:0; }.stored-state__list { display:grid; gap:10px; }.stored-state__list article { display:grid; gap:10px; padding:12px; border:1px solid var(--line); border-radius:8px; background:var(--bg); }.stored-state__list header { display:flex; align-items:center; justify-content:space-between; gap:12px; }.stored-state__list header small { color:var(--muted); font:10px 'DM Mono',monospace; }.stored-state__personas { display:grid; gap:6px; }.stored-state__personas > div { display:grid; grid-template-columns:minmax(130px,.8fr) minmax(150px,1fr) minmax(0,2fr); gap:10px; padding:8px 10px; border-radius:6px; background:var(--surface-raised); font-size:11px; }.stored-state__personas span { overflow:hidden; color:var(--muted); text-overflow:ellipsis; white-space:nowrap; }.stored-state details { color:var(--muted); font-size:11px; }.stored-state summary { cursor:pointer; }.stored-state pre { max-height:280px; overflow:auto; margin:8px 0 0; padding:10px; border-radius:6px; color:var(--text); background:var(--surface-raised); font:10px/1.55 'DM Mono',monospace; } @media(max-width:720px){.stored-state__personas > div { grid-template-columns:1fr; gap:4px; }} +.stored-state { display:grid; gap:12px; min-width:0; margin-bottom:16px; }.stored-state .panel-head { margin:0; }.stored-state__list { display:grid; min-width:0; gap:10px; }.stored-state__list article { display:grid; min-width:0; gap:10px; padding:12px; border:1px solid var(--line); border-radius:8px; background:var(--bg); }.stored-state__list header { display:flex; align-items:center; justify-content:space-between; gap:12px; }.stored-state__list header small { color:var(--muted); font:10px 'DM Mono',monospace; }.stored-state__personas { display:grid; min-width:0; gap:6px; }.stored-state__personas > div { display:grid; grid-template-columns:minmax(130px,.8fr) minmax(150px,1fr) minmax(0,2fr); gap:10px; padding:8px 10px; border-radius:6px; background:var(--surface-raised); font-size:11px; }.stored-state__personas span { overflow:hidden; color:var(--muted); text-overflow:ellipsis; white-space:nowrap; }.stored-state details { min-width:0; color:var(--muted); font-size:11px; }.stored-state summary { cursor:pointer; }.stored-state pre { box-sizing:border-box; width:100%; max-height:280px; min-width:0; overflow:auto; margin:8px 0 0; padding:10px; border-radius:6px; color:var(--text); background:var(--surface-raised); font:10px/1.55 'DM Mono',monospace; } @media(max-width:720px){.stored-state__personas > div { grid-template-columns:1fr; gap:4px; }} .cycle-interventions { display:grid; gap:12px; }.cycle-interventions > .hint { margin:0; }.intervention-list { display:grid; gap:10px; }.intervention-list article { display:grid; gap:9px; padding:14px; border:1px solid var(--line); border-radius:8px; background:var(--bg); }.intervention-list header { display:flex; justify-content:space-between; gap:12px; }.intervention-list header div { display:grid; gap:5px; }.intervention-list small { color:var(--muted); font-size:10px; }.intervention-list p { margin:0; color:var(--muted); line-height:1.55; }.intervention-list dl { display:flex; gap:20px; margin:0; }.intervention-list dl div { display:grid; gap:3px; }.intervention-list dt { color:var(--muted); font-size:9px; text-transform:uppercase; }.intervention-list dd { margin:0; font:11px 'DM Mono',monospace; }.intervention-list pre { margin:0; padding:9px; overflow:auto; border-radius:6px; background:var(--surface-raised); color:#8fb8ff; font:11px/1.5 'DM Mono',monospace; white-space:pre-wrap; } .proposal-history { display:grid; gap:14px; }.proposal-history .panel-head { margin-bottom:3px; }.proposal-history .hint { max-width:650px; }.proposal-filters { display:flex; gap:8px; flex-wrap:wrap; }.proposal-filters select { width:auto; max-width:230px; }.proposal-summary { display:flex; gap:8px; flex-wrap:wrap; }.proposal-summary span { display:flex; align-items:baseline; gap:6px; padding:8px 10px; border:1px solid var(--line); border-radius:7px; color:var(--muted); background:var(--bg); font-size:10px; }.proposal-summary b { color:var(--text); font:600 16px 'DM Mono',monospace; }.proposal-list { display:grid; border-top:1px solid var(--line); }.proposal-row { display:flex; justify-content:space-between; gap:14px; width:100%; padding:13px 2px; border:0; border-bottom:1px solid var(--line); color:var(--text); background:transparent; text-align:left; }.proposal-row:hover { background:var(--surface-raised); }.proposal-row > div:first-child { display:grid; min-width:0; gap:4px; }.proposal-row small,.proposal-row span { color:var(--muted); font-size:10px; }.proposal-row strong { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12px; }.proposal-row__meta { display:grid; justify-items:end; align-content:center; gap:6px; white-space:nowrap; }.modal--proposal-detail { width:min(760px,100%); }.proposal-detail { display:grid; gap:18px; }.proposal-detail__summary { display:flex; align-items:center; gap:10px; flex-wrap:wrap; color:var(--muted); font:10px 'DM Mono',monospace; }.proposal-detail section { display:grid; gap:8px; }.proposal-detail h3 { margin:0; font-size:12px; }.proposal-detail p { margin:0; color:var(--muted); font-size:12px; line-height:1.6; }.proposal-detail pre { margin:0; padding:10px; overflow:auto; border-radius:7px; background:var(--bg); color:#8fb8ff; font:11px/1.55 'DM Mono',monospace; white-space:pre-wrap; }.proposal-detail dl { display:flex; flex-wrap:wrap; gap:16px; margin:0; }.proposal-detail dl div { display:grid; min-width:120px; gap:3px; }.proposal-detail dt { color:var(--muted); font-size:9px; text-transform:uppercase; }.proposal-detail dd { max-width:480px; margin:0; overflow-wrap:anywhere; font:11px 'DM Mono',monospace; }.proposal-timeline { display:grid; gap:9px; margin:0; padding:0; list-style:none; }.proposal-timeline li { display:flex; align-items:center; gap:10px; padding-left:10px; border-left:2px solid var(--line); }.proposal-timeline li > div { display:grid; gap:3px; }.proposal-timeline strong { font-size:11px; }.proposal-timeline small { color:var(--muted); font-size:10px; } @media(max-width:720px){.proposal-history .trend-head { align-items:stretch; }.proposal-filters select { width:100%; max-width:none; }.proposal-row { align-items:flex-start; flex-direction:column; }.proposal-row__meta { justify-items:start; }.proposal-detail__summary { align-items:flex-start; flex-direction:column; }} .panel-title-action .panel-head { flex:1; }.panel-title-action__copy { flex:1; min-width:0; }.section-description { margin:5px 0 0; }.panel-title-action:has(.section-description) { margin-bottom:12px; }.panel-head:has(+ .section-description) { margin-bottom:0; }.panel-head + .section-description { margin:5px 0 12px; }.panel-title-action .approve { display:flex; align-items:center; gap:5px; white-space:nowrap; } From bb2a40417ab369b120fd243462782a3efe861a49 Mon Sep 17 00:00:00 2001 From: forthfate Date: Mon, 14 Sep 2026 18:49:24 +0900 Subject: [PATCH 06/14] feat: render stored persona state as a tree --- frontend/src/features/improvements/page.tsx | 18 +++++++++++++----- frontend/src/theme-overrides.css | 1 + 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/frontend/src/features/improvements/page.tsx b/frontend/src/features/improvements/page.tsx index 7a9334c..dc3265a 100644 --- a/frontend/src/features/improvements/page.tsx +++ b/frontend/src/features/improvements/page.tsx @@ -785,6 +785,15 @@ function StoredState({ .then(setStates) .catch(() => setStates([])); }, [buildId]); + const renderStateValue = (value: unknown, label?: string): ReactNode => { + if (Array.isArray(value)) { + return
    {label ?? "Array"}{value.length} items
      {value.map((item, index) =>
    • {renderStateValue(item, String(index))}
    • )}
    ; + } + if (value && typeof value === "object") { + return
    {label ?? "Object"}{Object.keys(value as Record).length} fields
      {Object.entries(value as Record).map(([key, item]) =>
    • {renderStateValue(item, key)}
    • )}
    ; + } + return {label}{value === null ? "null" : String(value)}; + }; return (
    @@ -808,11 +817,10 @@ function StoredState({ ? value as Record : {}; return ( -
    - {persona} - {String(record.last_action_at ?? "—")} - {String(record.next_check ?? record.last_summary ?? "—")} -
    +
    + {persona}{String(record.last_action_at ?? "—")}{String(record.active ?? "—")} +
      {Object.entries(record).map(([key, item]) =>
    • {renderStateValue(item, key)}
    • )}
    +
    ); })} diff --git a/frontend/src/theme-overrides.css b/frontend/src/theme-overrides.css index d58705b..e6f4910 100644 --- a/frontend/src/theme-overrides.css +++ b/frontend/src/theme-overrides.css @@ -56,6 +56,7 @@ footer { display:flex; align-items:center; justify-content:space-between; margin .analytics-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:14px; }.analytics-chart { min-width:0; padding:14px; border:1px solid var(--line); border-radius:8px; background:var(--bg); }.analytics-chart h3 { margin:0 0 12px; font-size:12px; }.analytics-chart .recharts-cartesian-axis-tick-value,.analytics-chart .recharts-legend-item-text { fill:var(--muted)!important; font-size:10px; }.analytics-chart .recharts-tooltip-wrapper { color:#182132; }.chart-select { display:flex; justify-content:flex-end; margin:-31px 0 10px; }.chart-select label { display:flex; align-items:center; gap:8px; color:var(--muted); font-size:11px; }.chart-select select { width:auto; max-width:320px; } @media(max-width:720px){.analytics-grid { grid-template-columns:1fr; }.chart-select { justify-content:flex-start; margin:0 0 10px; }} .improvements-build-selector { display:flex; justify-content:flex-end; margin-bottom:16px; }.improvements-build-selector label { display:flex; align-items:center; gap:8px; color:var(--muted); font-size:11px; }.improvements-build-selector select { width:auto; max-width:360px; } @media(max-width:720px){.improvements-build-selector { justify-content:flex-start; }} .stored-state { display:grid; gap:12px; min-width:0; margin-bottom:16px; }.stored-state .panel-head { margin:0; }.stored-state__list { display:grid; min-width:0; gap:10px; }.stored-state__list article { display:grid; min-width:0; gap:10px; padding:12px; border:1px solid var(--line); border-radius:8px; background:var(--bg); }.stored-state__list header { display:flex; align-items:center; justify-content:space-between; gap:12px; }.stored-state__list header small { color:var(--muted); font:10px 'DM Mono',monospace; }.stored-state__personas { display:grid; min-width:0; gap:6px; }.stored-state__personas > div { display:grid; grid-template-columns:minmax(130px,.8fr) minmax(150px,1fr) minmax(0,2fr); gap:10px; padding:8px 10px; border-radius:6px; background:var(--surface-raised); font-size:11px; }.stored-state__personas span { overflow:hidden; color:var(--muted); text-overflow:ellipsis; white-space:nowrap; }.stored-state details { min-width:0; color:var(--muted); font-size:11px; }.stored-state summary { cursor:pointer; }.stored-state pre { box-sizing:border-box; width:100%; max-height:280px; min-width:0; overflow:auto; margin:8px 0 0; padding:10px; border-radius:6px; color:var(--text); background:var(--surface-raised); font:10px/1.55 'DM Mono',monospace; } @media(max-width:720px){.stored-state__personas > div { grid-template-columns:1fr; gap:4px; }} +.stored-state__persona { padding:8px 10px; border-radius:6px; background:var(--surface-raised); }.stored-state__persona > summary { display:grid; grid-template-columns:minmax(130px,.8fr) minmax(150px,1fr) minmax(0,1fr); gap:10px; align-items:center; }.stored-state__persona > summary span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }.stored-state__tree { min-width:0; margin:9px 0 0 12px; padding-left:12px; border-left:1px solid var(--line); }.stored-state__tree-root { display:grid; gap:4px; margin:0; padding:0; list-style:none; }.stored-state__tree-node { margin:5px 0; }.stored-state__tree-node > summary { display:flex; justify-content:space-between; gap:10px; }.stored-state__tree-node > summary small { color:var(--muted); }.stored-state__tree-node ul { display:grid; gap:4px; margin:5px 0 0 12px; padding-left:12px; border-left:1px solid var(--line); list-style:none; }.stored-state__tree-leaf { display:grid; grid-template-columns:minmax(120px,.7fr) minmax(0,2fr); gap:10px; padding:4px 0; }.stored-state__tree-leaf code { min-width:0; overflow-wrap:anywhere; color:var(--text); white-space:pre-wrap; } @media(max-width:720px){.stored-state__persona > summary,.stored-state__tree-leaf { grid-template-columns:1fr; gap:4px; }} .cycle-interventions { display:grid; gap:12px; }.cycle-interventions > .hint { margin:0; }.intervention-list { display:grid; gap:10px; }.intervention-list article { display:grid; gap:9px; padding:14px; border:1px solid var(--line); border-radius:8px; background:var(--bg); }.intervention-list header { display:flex; justify-content:space-between; gap:12px; }.intervention-list header div { display:grid; gap:5px; }.intervention-list small { color:var(--muted); font-size:10px; }.intervention-list p { margin:0; color:var(--muted); line-height:1.55; }.intervention-list dl { display:flex; gap:20px; margin:0; }.intervention-list dl div { display:grid; gap:3px; }.intervention-list dt { color:var(--muted); font-size:9px; text-transform:uppercase; }.intervention-list dd { margin:0; font:11px 'DM Mono',monospace; }.intervention-list pre { margin:0; padding:9px; overflow:auto; border-radius:6px; background:var(--surface-raised); color:#8fb8ff; font:11px/1.5 'DM Mono',monospace; white-space:pre-wrap; } .proposal-history { display:grid; gap:14px; }.proposal-history .panel-head { margin-bottom:3px; }.proposal-history .hint { max-width:650px; }.proposal-filters { display:flex; gap:8px; flex-wrap:wrap; }.proposal-filters select { width:auto; max-width:230px; }.proposal-summary { display:flex; gap:8px; flex-wrap:wrap; }.proposal-summary span { display:flex; align-items:baseline; gap:6px; padding:8px 10px; border:1px solid var(--line); border-radius:7px; color:var(--muted); background:var(--bg); font-size:10px; }.proposal-summary b { color:var(--text); font:600 16px 'DM Mono',monospace; }.proposal-list { display:grid; border-top:1px solid var(--line); }.proposal-row { display:flex; justify-content:space-between; gap:14px; width:100%; padding:13px 2px; border:0; border-bottom:1px solid var(--line); color:var(--text); background:transparent; text-align:left; }.proposal-row:hover { background:var(--surface-raised); }.proposal-row > div:first-child { display:grid; min-width:0; gap:4px; }.proposal-row small,.proposal-row span { color:var(--muted); font-size:10px; }.proposal-row strong { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12px; }.proposal-row__meta { display:grid; justify-items:end; align-content:center; gap:6px; white-space:nowrap; }.modal--proposal-detail { width:min(760px,100%); }.proposal-detail { display:grid; gap:18px; }.proposal-detail__summary { display:flex; align-items:center; gap:10px; flex-wrap:wrap; color:var(--muted); font:10px 'DM Mono',monospace; }.proposal-detail section { display:grid; gap:8px; }.proposal-detail h3 { margin:0; font-size:12px; }.proposal-detail p { margin:0; color:var(--muted); font-size:12px; line-height:1.6; }.proposal-detail pre { margin:0; padding:10px; overflow:auto; border-radius:7px; background:var(--bg); color:#8fb8ff; font:11px/1.55 'DM Mono',monospace; white-space:pre-wrap; }.proposal-detail dl { display:flex; flex-wrap:wrap; gap:16px; margin:0; }.proposal-detail dl div { display:grid; min-width:120px; gap:3px; }.proposal-detail dt { color:var(--muted); font-size:9px; text-transform:uppercase; }.proposal-detail dd { max-width:480px; margin:0; overflow-wrap:anywhere; font:11px 'DM Mono',monospace; }.proposal-timeline { display:grid; gap:9px; margin:0; padding:0; list-style:none; }.proposal-timeline li { display:flex; align-items:center; gap:10px; padding-left:10px; border-left:2px solid var(--line); }.proposal-timeline li > div { display:grid; gap:3px; }.proposal-timeline strong { font-size:11px; }.proposal-timeline small { color:var(--muted); font-size:10px; } @media(max-width:720px){.proposal-history .trend-head { align-items:stretch; }.proposal-filters select { width:100%; max-width:none; }.proposal-row { align-items:flex-start; flex-direction:column; }.proposal-row__meta { justify-items:start; }.proposal-detail__summary { align-items:flex-start; flex-direction:column; }} .panel-title-action .panel-head { flex:1; }.panel-title-action__copy { flex:1; min-width:0; }.section-description { margin:5px 0 0; }.panel-title-action:has(.section-description) { margin-bottom:12px; }.panel-head:has(+ .section-description) { margin-bottom:0; }.panel-head + .section-description { margin:5px 0 12px; }.panel-title-action .approve { display:flex; align-items:center; gap:5px; white-space:nowrap; } From 53141a1db341341707bed651d170dbfb07fe6f14 Mon Sep 17 00:00:00 2001 From: forthfate Date: Mon, 14 Sep 2026 18:56:49 +0900 Subject: [PATCH 07/14] feat: split build creation into guided steps --- frontend/src/features/builds/page.tsx | 30 ++++++++++++++++++-------- frontend/src/locales/languages/en.json | 4 ++++ frontend/src/locales/languages/ja.json | 4 ++++ frontend/src/locales/languages/ko.json | 4 ++++ 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/frontend/src/features/builds/page.tsx b/frontend/src/features/builds/page.tsx index 3d59262..deefa17 100644 --- a/frontend/src/features/builds/page.tsx +++ b/frontend/src/features/builds/page.tsx @@ -85,6 +85,10 @@ export type ProfileFormCopy = { modelDeployment: LabelCopy; }; type BuildWizardCopy = { + basics: string; + evaluationSetup: string; + schedule: string; + decisionPolicy: string; buildId: LabelCopy; buildName: LabelCopy; runner: LabelCopy; @@ -356,7 +360,7 @@ function Direct({ return (
      - {[t.common.build, t.evaluation.criteria, t.ui.review].map( + {[copy.basics, copy.evaluationSetup, copy.schedule, copy.decisionPolicy, t.ui.review].map( (x, i) => (