feat(missions): document reader + three-tab IA for the mission page
The mission page made its own output unreadable. Reviewing a research
brief meant scrolling a 300px <pre> nested inside a 260px run box nested
inside the page scroller (plus a 4th scroll region for the description) —
and the text was capped at 6,000 chars server-side with no way to fetch
the rest, so a 53kB brief showed ~11% of itself and silently dropped the
remainder. Eight flat tabs (overview/phases/tasks/team/live/artifacts/
benchmarks/pane) mixed lifecycle, work items, people, telemetry, outputs
and infra at one level, so nothing indicated where the deliverable lived.
Reader:
- GET /api/missions/{id}/documents lists every agent output (titles +
sizes, no bodies); GET .../documents/{run_id}/{index} returns one in
full. Scoped to the mission so a run id from elsewhere can't be read.
- MissionOutputReader: rail (documents grouped by phase) · document ·
outline (headings, click to jump). Exactly one scroll container per
column, never nested. Copy + download .md.
- MarkdownBlock gains fenced code blocks (agent output is full of ```rust,
previously mangled into paragraphs), h4-h6, heading anchors, and an
outlineOf() helper.
Information architecture:
- Three primary tabs with shallow sub-views: RUN (phases/tasks/live) ·
OUTPUT (documents/artifacts/benchmarks) · SETUP (overview/team/pane).
- PhaseRunsList shows a short excerpt with no inner scrollbar and points
at the reader for the full text.
- The header description is clipped, not scrollable; its full text now
has a home in Setup → Overview.
Missions list:
- /api/missions returns MissionListItem — Mission flattened plus
phases_total/phases_done/current_phase, so the JSON stays a strict
superset. Cards render a progress bar and "Coding · 1/2" instead of a
bare status dot.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d676a9e089
commit
0785ac9c79
@@ -141,6 +141,14 @@ export function PhaseRunsList({ runs }: { runs: MissionRunSummary[] }) {
|
||||
);
|
||||
}
|
||||
|
||||
/** First few lines of a turn — enough to recognize it in the timeline,
|
||||
* short enough not to need its own scrollbar. */
|
||||
function excerpt(text: string, lines = 12): string {
|
||||
const parts = text.split("\n");
|
||||
if (parts.length <= lines) return text;
|
||||
return `${parts.slice(0, lines).join("\n")}\n…`;
|
||||
}
|
||||
|
||||
function RunOutputPanel({ runId }: { runId: string }) {
|
||||
const [data, setData] = useState<RunOutput | null>(null);
|
||||
const [err, setErr] = useState<string | null>(null);
|
||||
@@ -238,6 +246,10 @@ function RunOutputPanel({ runId }: { runId: string }) {
|
||||
</span>
|
||||
)}
|
||||
</summary>
|
||||
{/* A SHORT excerpt only — no inner scrollbar. This used to be
|
||||
a 300px scroll box nested inside the 260px run box inside
|
||||
the page scroller, which made long output unreadable. The
|
||||
full document lives in the Output tab's reader. */}
|
||||
<pre
|
||||
style={{
|
||||
margin: "4px 0 0",
|
||||
@@ -248,12 +260,24 @@ function RunOutputPanel({ runId }: { runId: string }) {
|
||||
fontSize: 10.5,
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
maxHeight: 300,
|
||||
overflow: "auto",
|
||||
}}
|
||||
>
|
||||
{o.preview}
|
||||
{excerpt(o.preview)}
|
||||
</pre>
|
||||
<span
|
||||
style={{
|
||||
display: "block",
|
||||
marginTop: 4,
|
||||
fontSize: 10,
|
||||
color: "#6a6a72",
|
||||
}}
|
||||
>
|
||||
{o.full_len.toLocaleString()} chars · open the{" "}
|
||||
<strong style={{ color: "#7cd6e0", fontWeight: 600 }}>
|
||||
Output
|
||||
</strong>{" "}
|
||||
tab to read this in full
|
||||
</span>
|
||||
</details>
|
||||
);
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user