missions: collapse each run turn + collapse phase summary card
ci / gates (push) Successful in 6s
ci / frontend (push) Successful in 27s
ci / rust (push) Successful in 4m30s
ci / e2e (push) Skipped
ci / publish (push) Successful in 36s

- RunOutputPanel: each turn now renders as <details> with a 1-line
  peek in the summary. First turn open by default (so operators see
  something without a click), subsequent turns collapsed. Same shape
  applies to research + coding runs (shared component).
- PhaseSummaryCard: click the header to collapse the whole card;
  narrative peek shows in the collapsed state. State persisted per
  phase_id in localStorage so it stays remembered across visits.
- PhaseSummaryCard Section: cap max height at 280px with internal
  scroll so long tooling / sources / next-action lists dont blow
  out the card height.
This commit is contained in:
Omar Sobh
2026-07-23 19:26:28 -07:00
parent c4ecb9baa4
commit 9a23c851e0
2 changed files with 145 additions and 61 deletions
@@ -196,40 +196,67 @@ function RunOutputPanel({ runId }: { runId: string }) {
to reach their working directory or found nothing to act on.
</span>
) : (
data.outputs.map((o, i) => (
<div key={i}>
<div
style={{
color: "#5ec8d8",
fontSize: 9,
letterSpacing: ".08em",
textTransform: "uppercase",
marginBottom: 3,
}}
>
turn {i + 1}
{o.truncated
? ` · showing first ${o.preview.length.toLocaleString()} of ${o.full_len.toLocaleString()} chars`
: ""}
</div>
<pre
style={{
margin: 0,
padding: 6,
borderRadius: 4,
background: "rgba(0,0,0,.5)",
color: "#e0e0e5",
fontSize: 10.5,
whiteSpace: "pre-wrap",
wordBreak: "break-word",
maxHeight: 300,
overflow: "auto",
}}
>
{o.preview}
</pre>
</div>
))
data.outputs.map((o, i) => {
// First turn open by default so the operator sees something
// without a click; every subsequent turn collapses so the
// panel stays glanceable — click to expand each turn.
const firstLine =
o.preview
.split("\n")
.find((l) => l.trim().length > 0)
?.trim() ?? "";
const preview =
firstLine.length > 140 ? `${firstLine.slice(0, 140)}…` : firstLine;
return (
<details key={i} open={i === 0}>
<summary
style={{
cursor: "pointer",
color: "#5ec8d8",
fontSize: 10,
letterSpacing: ".06em",
textTransform: "uppercase",
padding: "3px 0",
listStyle: "revert",
}}
>
turn {i + 1}
{o.truncated
? ` · ${o.preview.length.toLocaleString()}/${o.full_len.toLocaleString()} chars`
: ""}
{preview && (
<span
style={{
color: "#8a8a92",
textTransform: "none",
letterSpacing: 0,
marginLeft: 8,
fontSize: 10,
}}
>
· {preview}
</span>
)}
</summary>
<pre
style={{
margin: "4px 0 0",
padding: 6,
borderRadius: 4,
background: "rgba(0,0,0,.5)",
color: "#e0e0e5",
fontSize: 10.5,
whiteSpace: "pre-wrap",
wordBreak: "break-word",
maxHeight: 300,
overflow: "auto",
}}
>
{o.preview}
</pre>
</details>
);
})
)}
</div>
);