feat: show prepared workflow input files
This commit is contained in:
@@ -131,6 +131,16 @@ describe("DemoWorkflowScene", () => {
|
|||||||
expect(screen.getByLabelText("workflow graph")).toBeInTheDocument();
|
expect(screen.getByLabelText("workflow graph")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("renders factual input files on the prepared workflow input beat", () => {
|
||||||
|
renderBeat("input");
|
||||||
|
|
||||||
|
const browser = screen.getByRole("region", { name: /workflow input files/i });
|
||||||
|
expect(within(browser).getByText("project-brief.md")).toBeInTheDocument();
|
||||||
|
expect(within(browser).getByText("architecture-notes.md")).toBeInTheDocument();
|
||||||
|
expect(within(browser).getByRole("group", { name: /workflow output/i })).toHaveTextContent("issue-board.json");
|
||||||
|
expect(screen.queryByRole("region", { name: /workflow input summary/i })).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it("renders the guided interrupt contract via product moment in typed-human-boundary", () => {
|
it("renders the guided interrupt contract via product moment in typed-human-boundary", () => {
|
||||||
renderBeat("interrupt", "typed-human-boundary");
|
renderBeat("interrupt", "typed-human-boundary");
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { GuidedProductMoment } from "./GuidedProductMoment.js";
|
|||||||
import { InterruptContractPreview } from "./InterruptContractPreview.js";
|
import { InterruptContractPreview } from "./InterruptContractPreview.js";
|
||||||
import { NodeSpotlight } from "./NodeSpotlight.js";
|
import { NodeSpotlight } from "./NodeSpotlight.js";
|
||||||
import { OperationBlock } from "./OperationBlock.js";
|
import { OperationBlock } from "./OperationBlock.js";
|
||||||
import { RunInputFacts } from "./RunFactsPanel.js";
|
import { RunInputFileBrowser } from "./RunInputFileBrowser.js";
|
||||||
import { StageCaption } from "./StageCaption.js";
|
import { StageCaption } from "./StageCaption.js";
|
||||||
import type { SceneBeatDefinition, SceneDefinition } from "./storyboard.js";
|
import type { SceneBeatDefinition, SceneDefinition } from "./storyboard.js";
|
||||||
import type { PresentationTargetHealth } from "./presentation-target-status.js";
|
import type { PresentationTargetHealth } from "./presentation-target-status.js";
|
||||||
@@ -160,7 +160,10 @@ export const DemoWorkflowScene = ({
|
|||||||
|
|
||||||
{beat.id === "input" && (
|
{beat.id === "input" && (
|
||||||
<div className="demo-workflow-stage__input-facts">
|
<div className="demo-workflow-stage__input-facts">
|
||||||
<RunInputFacts facts={facts} />
|
<RunInputFileBrowser
|
||||||
|
selectedDocuments={facts.input.selectedDocuments}
|
||||||
|
boardPath={facts.input.boardPath}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { cleanup, render, screen, within } from "@testing-library/react";
|
||||||
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
|
import { RunInputFileBrowser } from "./RunInputFileBrowser.js";
|
||||||
|
|
||||||
|
afterEach(() => cleanup());
|
||||||
|
|
||||||
|
describe("RunInputFileBrowser", () => {
|
||||||
|
it("renders the selected docs paths as factual readable files", () => {
|
||||||
|
render(
|
||||||
|
<RunInputFileBrowser
|
||||||
|
selectedDocuments={["docs/project-brief.md", "docs/architecture-notes.md"]}
|
||||||
|
boardPath="artifacts/issue-board.json"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const browser = screen.getByRole("region", { name: /workflow input files/i });
|
||||||
|
expect(within(browser).getByRole("heading", { name: "docs/" })).toBeInTheDocument();
|
||||||
|
|
||||||
|
const files = within(browser).getByRole("list", { name: /selected for this run/i });
|
||||||
|
expect(within(files).getAllByRole("listitem")).toHaveLength(2);
|
||||||
|
for (const path of ["docs/project-brief.md", "docs/architecture-notes.md"]) {
|
||||||
|
const row = within(files).getByText(path).closest("li");
|
||||||
|
expect(row).not.toBeNull();
|
||||||
|
expect(row).toHaveAttribute("data-file-path", path);
|
||||||
|
expect(row).toHaveTextContent(/selected|read/i);
|
||||||
|
}
|
||||||
|
|
||||||
|
const destination = within(browser).getByRole("group", { name: /workflow output/i });
|
||||||
|
expect(destination).toHaveTextContent("artifacts/issue-board.json");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not invent file metadata or contents", () => {
|
||||||
|
render(
|
||||||
|
<RunInputFileBrowser
|
||||||
|
selectedDocuments={["docs/project-brief.md"]}
|
||||||
|
boardPath="issue-board.json"
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const browser = screen.getByRole("region", { name: /workflow input files/i });
|
||||||
|
expect(within(browser).queryByText(/\b\d+(?:\.\d+)?\s*(?:kb|mb|bytes)\b/i)).not.toBeInTheDocument();
|
||||||
|
expect(within(browser).queryByText(/preview|modified|created|content/i)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
export type RunInputFileBrowserProps = {
|
||||||
|
readonly selectedDocuments: ReadonlyArray<string>;
|
||||||
|
readonly boardPath: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RunInputFileBrowser = ({
|
||||||
|
selectedDocuments,
|
||||||
|
boardPath,
|
||||||
|
}: RunInputFileBrowserProps) => (
|
||||||
|
<section className="run-input-file-browser" role="region" aria-label="workflow input files">
|
||||||
|
<header className="run-input-file-browser__header">
|
||||||
|
<h3>docs/</h3>
|
||||||
|
<span>selected for this run</span>
|
||||||
|
</header>
|
||||||
|
<ul className="run-input-file-browser__list" aria-label="selected for this run">
|
||||||
|
{selectedDocuments.map((path) => (
|
||||||
|
<li className="run-input-file-browser__file" data-file-path={path} key={path}>
|
||||||
|
<span className="run-input-file-browser__icon" aria-hidden="true">file</span>
|
||||||
|
<code>{path}</code>
|
||||||
|
<span className="run-input-file-browser__marker">selected / read</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<div className="run-input-file-browser__destination" role="group" aria-label="workflow output">
|
||||||
|
<span>workflow output</span>
|
||||||
|
<code>{boardPath}</code>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
@@ -806,6 +806,114 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.demo-workflow-stage__input-facts {
|
||||||
|
display: grid;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-input-file-browser {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto minmax(0, 1fr) auto;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid color-mix(in oklch, var(--accent-cyan) 28%, var(--stage-line));
|
||||||
|
border-radius: 0.8rem;
|
||||||
|
padding: 0.8rem;
|
||||||
|
background:
|
||||||
|
linear-gradient(145deg, oklch(0.72 0.17 195 / 0.08), transparent 45%),
|
||||||
|
oklch(0.095 0.02 250 / 0.96);
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-input-file-browser__header,
|
||||||
|
.run-input-file-browser__destination {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.75rem;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-input-file-browser__header {
|
||||||
|
padding-bottom: 0.65rem;
|
||||||
|
border-bottom: 1px solid var(--stage-line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-input-file-browser__header h3 {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font: 700 1rem/1 var(--font-mono, monospace);
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-input-file-browser__header span,
|
||||||
|
.run-input-file-browser__destination > span,
|
||||||
|
.run-input-file-browser__marker {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font: 650 0.6rem/1.2 var(--font-mono, monospace);
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-input-file-browser__list {
|
||||||
|
display: grid;
|
||||||
|
align-content: start;
|
||||||
|
gap: 0.4rem;
|
||||||
|
min-height: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.7rem 0;
|
||||||
|
overflow: auto;
|
||||||
|
list-style: none;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-input-file-browser__file {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||||
|
gap: 0.55rem;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0.5rem 0.55rem;
|
||||||
|
border: 1px solid color-mix(in oklch, var(--stage-line) 72%, transparent);
|
||||||
|
border-radius: 0.55rem;
|
||||||
|
background: oklch(0.13 0.025 250 / 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-input-file-browser__icon {
|
||||||
|
color: var(--accent-cyan);
|
||||||
|
font: 650 0.56rem/1 var(--font-mono, monospace);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-input-file-browser__file code,
|
||||||
|
.run-input-file-browser__destination code {
|
||||||
|
min-width: 0;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font: 600 0.75rem/1.35 var(--font-mono, monospace);
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-input-file-browser__marker {
|
||||||
|
color: var(--accent-cyan);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-input-file-browser__destination {
|
||||||
|
padding-top: 0.7rem;
|
||||||
|
border-top: 1px solid var(--stage-line);
|
||||||
|
}
|
||||||
|
|
||||||
|
@container presentation-canvas (max-width: 620px) {
|
||||||
|
.run-input-file-browser__file {
|
||||||
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.run-input-file-browser__marker {
|
||||||
|
grid-column: 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.interrupt-contract-preview__schema,
|
.interrupt-contract-preview__schema,
|
||||||
.interrupt-contract-preview__payload,
|
.interrupt-contract-preview__payload,
|
||||||
.interrupt-contract-preview__issues {
|
.interrupt-contract-preview__issues {
|
||||||
|
|||||||
Reference in New Issue
Block a user