feat(ui): three-screen mission wizard that asks only what the type needs
Five fixed steps for every mission type, and getting a research document out of it meant naming a team, choosing a runtime, and writing per-phase completion conditions under a paragraph explaining what a model checker can and cannot prove. Two of those steps asked for things the mission does not use, and one of them blocked outright. 1 What do you want to do? 2 Title, a description with a Polish button, repo ONLY if the type needs one 3 Review -> Launch, plus one collapsed Advanced section Two hard defects fixed on the way: - The microVM runtime could not be selected AT ALL. Step 4 gated Next on `targetNodeId`, which microVM deliberately never sets because placement picks the node per phase. Everything shipped today, the local-GPU backend included, was unreachable from the UI. - Step 3 required a team while every workflow TOML already names one in `default_team_template` — which this file ignored. The answer was always available and the question was always asked. It is now resolved by key, with a category fallback, and shown under Advanced so an operator can see WHICH default rather than having to supply one. A failed `/api/team-templates` request and a genuinely empty list rendered the identical red banner, which sends the reader looking for missing template files when the request had 401'd. They now say different things. `phases[]` is no longer sent unless someone set a completion condition. `recipeToPreset` strips each phase's `config`, so posting the stripped list overrode the recipe's real settings — tools, commit policy, loop mode — with nothing. Omitting it lets `phases_for_create` use the recipe, which is both simpler and more correct. Launch keeps its own gate, since Advanced can still produce an unlaunchable combination — but it names what is missing instead of greying out in silence. Artifacts get a Download link. Deliberately a plain link to the streaming route rather than a Blob built from what "Read" already fetched: that content is capped at 2 MiB and UTF-8-decoded, so reusing it would silently produce a truncated or undownloadable file for exactly the artifacts worth downloading. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
25f075a8be
commit
e4bddeb1ba
@@ -11,7 +11,7 @@
|
|||||||
// research phase can leave dozens of documents.
|
// research phase can leave dozens of documents.
|
||||||
|
|
||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { FileText, GitMerge } from "lucide-react";
|
import { Download, FileText, GitMerge } from "lucide-react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getArtifactContent,
|
getArtifactContent,
|
||||||
@@ -172,6 +172,28 @@ export function MissionArtifacts({
|
|||||||
>
|
>
|
||||||
{isOpen ? "Hide" : "Read"}
|
{isOpen ? "Hide" : "Read"}
|
||||||
</button>
|
</button>
|
||||||
|
{/* A plain link, not a fetch-and-Blob.
|
||||||
|
The read endpoint caps at 2 MiB and decodes as UTF-8, so
|
||||||
|
building the download from what "Read" already fetched would
|
||||||
|
inherit both limits and silently produce a truncated or
|
||||||
|
undownloadable file for exactly the artifacts worth
|
||||||
|
downloading. This hits the streaming route instead. */}
|
||||||
|
<a
|
||||||
|
href={`/api/missions/${missionId}/artifacts/${a.id}/download`}
|
||||||
|
download
|
||||||
|
style={{
|
||||||
|
...secondaryBtn,
|
||||||
|
padding: "5px 10px",
|
||||||
|
fontSize: 11,
|
||||||
|
textDecoration: "none",
|
||||||
|
display: "inline-flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Download aria-hidden size={12} />
|
||||||
|
Download
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div
|
<div
|
||||||
@@ -194,7 +216,8 @@ export function MissionArtifacts({
|
|||||||
</div>
|
</div>
|
||||||
) : text?.truncated ? (
|
) : text?.truncated ? (
|
||||||
<div style={{ fontFamily: mono, fontSize: 11.5, color: "#8a8a92" }}>
|
<div style={{ fontFamily: mono, fontSize: 11.5, color: "#8a8a92" }}>
|
||||||
too large to display inline ({text.bytes.toLocaleString()} bytes)
|
too large to display inline ({text.bytes.toLocaleString()}{" "}
|
||||||
|
bytes) — use Download
|
||||||
</div>
|
</div>
|
||||||
) : text ? (
|
) : text ? (
|
||||||
<MarkdownBlock source={text.content} />
|
<MarkdownBlock source={text.content} />
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -465,11 +465,16 @@ export interface TemplatePreset {
|
|||||||
blurb: string;
|
blurb: string;
|
||||||
requiresRepo: boolean;
|
requiresRepo: boolean;
|
||||||
phases: PhaseSpec[];
|
phases: PhaseSpec[];
|
||||||
|
/** The recipe's own team choice. Every workflow TOML declares one and the
|
||||||
|
* wizard used to ignore it, which is why step 3 asked the user to pick a
|
||||||
|
* team the mission had already chosen. */
|
||||||
|
defaultTeamTemplate?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TEMPLATE_PRESETS: TemplatePreset[] = [
|
export const TEMPLATE_PRESETS: TemplatePreset[] = [
|
||||||
{
|
{
|
||||||
kind: "research_only",
|
kind: "research_only",
|
||||||
|
defaultTeamTemplate: "rust_sdlc",
|
||||||
title: "Research only",
|
title: "Research only",
|
||||||
blurb:
|
blurb:
|
||||||
"Produce a styled MD + PDF artifact in the workspace. One-shot or scheduled.",
|
"Produce a styled MD + PDF artifact in the workspace. One-shot or scheduled.",
|
||||||
@@ -478,6 +483,7 @@ export const TEMPLATE_PRESETS: TemplatePreset[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "research_and_code",
|
kind: "research_and_code",
|
||||||
|
defaultTeamTemplate: "rust_sdlc",
|
||||||
title: "Research + Coding Loop",
|
title: "Research + Coding Loop",
|
||||||
blurb:
|
blurb:
|
||||||
"Research a topic against a repo, then loop the coding team through the produced INT-XX items until done.",
|
"Research a topic against a repo, then loop the coding team through the produced INT-XX items until done.",
|
||||||
@@ -489,6 +495,7 @@ export const TEMPLATE_PRESETS: TemplatePreset[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "security_hardening",
|
kind: "security_hardening",
|
||||||
|
defaultTeamTemplate: "rust_sdlc",
|
||||||
title: "Security Hardening",
|
title: "Security Hardening",
|
||||||
blurb:
|
blurb:
|
||||||
"Scan the repo for vulnerabilities, research patches, then apply + verify.",
|
"Scan the repo for vulnerabilities, research patches, then apply + verify.",
|
||||||
@@ -501,6 +508,7 @@ export const TEMPLATE_PRESETS: TemplatePreset[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "refactor",
|
kind: "refactor",
|
||||||
|
defaultTeamTemplate: "rust_sdlc",
|
||||||
title: "Refactor",
|
title: "Refactor",
|
||||||
blurb:
|
blurb:
|
||||||
"Audit dependencies + versions, propose API/SDK adaptations, apply the changes.",
|
"Audit dependencies + versions, propose API/SDK adaptations, apply the changes.",
|
||||||
@@ -509,6 +517,7 @@ export const TEMPLATE_PRESETS: TemplatePreset[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
kind: "benchmark",
|
kind: "benchmark",
|
||||||
|
defaultTeamTemplate: "rust_sdlc",
|
||||||
title: "Benchmark",
|
title: "Benchmark",
|
||||||
blurb:
|
blurb:
|
||||||
"Author + baseline benchmarks so subsequent refactors can be measured before/after.",
|
"Author + baseline benchmarks so subsequent refactors can be measured before/after.",
|
||||||
@@ -557,4 +566,5 @@ export const recipeToPreset = (r: WorkflowRecipe): TemplatePreset => ({
|
|||||||
.slice()
|
.slice()
|
||||||
.sort((a, b) => a.order_idx - b.order_idx)
|
.sort((a, b) => a.order_idx - b.order_idx)
|
||||||
.map((p) => ({ kind: p.kind, order_idx: p.order_idx })),
|
.map((p) => ({ kind: p.kind, order_idx: p.order_idx })),
|
||||||
|
defaultTeamTemplate: r.default_team_template ?? null,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user