feat: expose factual run input browser
This commit is contained in:
@@ -329,10 +329,11 @@ separate activity after these surfaces are stable.
|
||||
Story-flow audit: [`presentation story audit`](runbooks/presentation-story-audit.md);
|
||||
dated evidence: [`presentation rehearsal log`](runbooks/presentation-rehearsal-log.md).
|
||||
|
||||
9. **Deferred: factual input file browser:** replace the Scene 10 input
|
||||
manifest-only view with a read-only browser backed by canonical prepared or
|
||||
live run facts. Distinguish declared, selected, read, and produced files;
|
||||
do not imply file reads without evidence.
|
||||
9. **Completed: factual input file browser:** Scene 10 presents the run-selected
|
||||
documents as a read-only browser with selectable prepared-fixture Markdown
|
||||
excerpts and a separately labelled output destination. The preview states
|
||||
that it is not execution evidence; the UI does not claim per-file reads
|
||||
because the current trace recording does not expose them.
|
||||
|
||||
10. **Completed: presentation follow-up visual/story pass:** clarified Scene 1
|
||||
and the lifecycle-to-authoring narrative, gave Scenes 7 and 9 a stronger
|
||||
|
||||
@@ -116,8 +116,8 @@ describe("DemoWorkflowScene", () => {
|
||||
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("button", { name: /project-brief\.md/i })).toBeInTheDocument();
|
||||
expect(within(browser).getByRole("button", { name: /architecture-notes\.md/i })).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();
|
||||
});
|
||||
|
||||
@@ -37,7 +37,7 @@ describe("PresentationFooter", () => {
|
||||
);
|
||||
const footer = screen.getByRole("contentinfo", { name: /presentation footer/i });
|
||||
expect(within(footer).getByText("6 / 14")).toBeInTheDocument();
|
||||
expect(within(footer).getByText("3 / 4")).toBeInTheDocument();
|
||||
expect(within(footer).getByText("4 / 5")).toBeInTheDocument();
|
||||
expect(within(footer).getByText(/workflow\.runs\.trace/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
import { cleanup, render, screen, within } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { RunInputFileBrowser } from "./RunInputFileBrowser.js";
|
||||
|
||||
afterEach(() => cleanup());
|
||||
|
||||
const renderBrowser = () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<RunInputFileBrowser
|
||||
selectedDocuments={["project-brief.md", "architecture-notes.md"]}
|
||||
boardPath="issue-board.json"
|
||||
/>,
|
||||
);
|
||||
return { user };
|
||||
};
|
||||
|
||||
describe("RunInputFileBrowser", () => {
|
||||
it("renders included docs paths as factual prepared-run files", () => {
|
||||
it("renders selected docs as an interactive file list", () => {
|
||||
render(
|
||||
<RunInputFileBrowser
|
||||
selectedDocuments={["docs/project-brief.md", "docs/architecture-notes.md"]}
|
||||
@@ -18,30 +30,45 @@ describe("RunInputFileBrowser", () => {
|
||||
|
||||
const files = within(browser).getByRole("list", { name: /included in prepared run/i });
|
||||
expect(within(files).getAllByRole("listitem")).toHaveLength(2);
|
||||
expect(within(files).getAllByText(/included in prepared run/i)).toHaveLength(2);
|
||||
expect(within(files).queryAllByRole("button")).toHaveLength(0);
|
||||
expect(within(files).getAllByText("selected")).toHaveLength(2);
|
||||
expect(within(files).getAllByRole("button")).toHaveLength(2);
|
||||
expect(within(files).queryAllByRole("link")).toHaveLength(0);
|
||||
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(/included in prepared run/i);
|
||||
expect(row).toHaveTextContent(/selected/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", () => {
|
||||
it("previews the selected prepared fixture without calling it run evidence", async () => {
|
||||
const { user } = renderBrowser();
|
||||
|
||||
expect(screen.getByRole("region", { name: /prepared fixture preview/i })).toHaveTextContent(
|
||||
/workflow substrate for ai-agent-facing workspace automation/i,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /architecture-notes\.md/i }));
|
||||
|
||||
const preview = screen.getByRole("region", { name: /prepared fixture preview/i });
|
||||
expect(preview).toHaveTextContent(/architecture-notes\.md/i);
|
||||
expect(preview).toHaveTextContent(/fixture preview/i);
|
||||
expect(preview).toHaveTextContent(/not execution evidence/i);
|
||||
});
|
||||
|
||||
it("shows an honest empty preview for files absent from the fixture catalog", () => {
|
||||
render(
|
||||
<RunInputFileBrowser
|
||||
selectedDocuments={["docs/project-brief.md"]}
|
||||
selectedDocuments={["live-only.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();
|
||||
expect(screen.getByRole("region", { name: /prepared fixture preview/i })).toHaveTextContent(
|
||||
/preview unavailable/i,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { preparedInputFixture } from "./run-input-fixtures.js";
|
||||
|
||||
export type RunInputFileBrowserProps = {
|
||||
readonly selectedDocuments: ReadonlyArray<string>;
|
||||
readonly boardPath: string;
|
||||
@@ -6,24 +9,48 @@ export type RunInputFileBrowserProps = {
|
||||
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>included in prepared run</span>
|
||||
</header>
|
||||
<ul className="run-input-file-browser__list" aria-label="included in prepared 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">included in prepared run</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>
|
||||
);
|
||||
}: RunInputFileBrowserProps) => {
|
||||
const [selectedPath, setSelectedPath] = useState(selectedDocuments[0] ?? "");
|
||||
const visibleSelectedPath = selectedDocuments.includes(selectedPath)
|
||||
? selectedPath
|
||||
: selectedDocuments[0] ?? "";
|
||||
const fixture = preparedInputFixture(visibleSelectedPath);
|
||||
|
||||
return (
|
||||
<section className="run-input-file-browser" aria-label="workflow input files">
|
||||
<header className="run-input-file-browser__header">
|
||||
<h3>docs/</h3>
|
||||
<span>{selectedDocuments.length} selected by run input</span>
|
||||
</header>
|
||||
<div className="run-input-file-browser__body">
|
||||
<ul className="run-input-file-browser__list" aria-label="included in prepared run">
|
||||
{selectedDocuments.map((path) => (
|
||||
<li className="run-input-file-browser__file" data-file-path={path} key={path}>
|
||||
<button
|
||||
type="button"
|
||||
data-selected={path === visibleSelectedPath}
|
||||
aria-pressed={path === visibleSelectedPath}
|
||||
onClick={() => setSelectedPath(path)}
|
||||
>
|
||||
<span className="run-input-file-browser__icon" aria-hidden="true">md</span>
|
||||
<code>{path}</code>
|
||||
<span className="run-input-file-browser__marker">selected</span>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<article className="run-input-file-browser__preview" role="region" aria-label="prepared fixture preview">
|
||||
<header>
|
||||
<code>{fixture?.name ?? visibleSelectedPath}</code>
|
||||
<span>Fixture preview · not execution evidence</span>
|
||||
</header>
|
||||
{fixture ? <pre>{fixture.markdown}</pre> : <p>Preview unavailable for this selected input.</p>}
|
||||
</article>
|
||||
</div>
|
||||
<div className="run-input-file-browser__destination" role="group" aria-label="workflow output">
|
||||
<span>declared output destination</span>
|
||||
<code>{boardPath}</code>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ describe("SceneProgress", () => {
|
||||
render(<SceneProgress location={location} />);
|
||||
|
||||
expect(screen.getByText("6 / 14")).toBeInTheDocument();
|
||||
expect(screen.getByText("3 / 4")).toBeInTheDocument();
|
||||
expect(screen.getByText("4 / 5")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows scene position for run-from-deployment/graph", () => {
|
||||
|
||||
@@ -7,7 +7,7 @@ const expectedBeats = {
|
||||
positioning: ["landscape", "lda-position"],
|
||||
"planner-runtime": ["planner", "runtime", "boundary"],
|
||||
lifecycle: ["draft", "artifact", "deployment", "run"],
|
||||
architecture: ["client", "api", "runtime", "node-use"],
|
||||
architecture: ["overview", "client", "api", "runtime", "node-use"],
|
||||
authoring: ["discover", "author", "diagnose", "repair"],
|
||||
"agent-handoff": ["request"],
|
||||
"prepared-lifecycle": ["discover", "draft", "validate", "artifact", "deployment"],
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
export type PreparedInputFixture = {
|
||||
readonly name: string;
|
||||
readonly markdown: string;
|
||||
};
|
||||
|
||||
// These excerpts mirror examples/lda_report_workflow/documents for an offline
|
||||
// presentation preview. Selection is run evidence; this content is not.
|
||||
const preparedInputFixtures: Readonly<Record<string, string>> = {
|
||||
"project-brief.md": `# Project Brief
|
||||
|
||||
lda.chat is a workflow substrate for AI-agent-facing workspace automation. The
|
||||
prototype separates external planning from deterministic workflow execution.
|
||||
|
||||
Key achievements:
|
||||
|
||||
- Typed Draft, Artifact, Deployment, Run, and Trace lifecycle records.
|
||||
- Source-provider boundary for platform, MCP, Python, and experimental OpenAPI sources.
|
||||
- JSON-RPC and CLI surfaces usable by external agents and human operators.`,
|
||||
"architecture-notes.md": `# Architecture Notes
|
||||
|
||||
Lifecycle:
|
||||
|
||||
- Drafts are mutable authoring workspaces with revisions.
|
||||
- Artifacts are immutable versioned workflow definitions.
|
||||
- Deployments bind logical source ids to configured concrete sources.
|
||||
- Runs persist stopped execution records and bounded traces.
|
||||
|
||||
Runtime:
|
||||
|
||||
- The core executes typed graph nodes and routes by declared outcomes.
|
||||
- Interrupt nodes pause at explicit human-in-the-loop boundaries.`,
|
||||
"evaluation-findings.md": `# Evaluation Findings
|
||||
|
||||
Evidence:
|
||||
|
||||
- Automated tests cover core runtime, artifacts, deployments, CLI, JSON-RPC,
|
||||
source providers, and examples.
|
||||
- A 36-trial audited agent challenge campaign evaluated the product-facing CLI
|
||||
under bounded conditions.
|
||||
|
||||
Limitations:
|
||||
|
||||
- Agent challenge runs are operational evidence, not a controlled model study.
|
||||
- The prototype does not claim production security, scheduling, RBAC, or a
|
||||
general autonomous planning algorithm.`,
|
||||
"risk-register.md": `# Risk Register
|
||||
|
||||
Material risks:
|
||||
|
||||
- Title and product framing can overstate the implemented autonomous-agent layer.
|
||||
- Evaluation evidence is stronger as systems evidence than as a controlled study.
|
||||
- File-backed stores are useful for auditability but not a production transaction boundary.
|
||||
|
||||
Mitigations:
|
||||
|
||||
- Keep the agent/substrate boundary explicit in the thesis and defense.
|
||||
- Present challenge data as bounded operational evidence.`,
|
||||
"roadmap.md": `# Roadmap
|
||||
|
||||
Near-term:
|
||||
|
||||
- Add self-describing interrupt request and resume contracts.
|
||||
- Build a deterministic lda.chat report workflow with typed issue approval.
|
||||
- Build a local Workflow Console over JSON-RPC.
|
||||
- Add live-demo replay support for the defense.
|
||||
|
||||
Later:
|
||||
|
||||
- Add production secret stores and transactional persistence.
|
||||
- Add a surrounding agent interface and planner loop.`,
|
||||
};
|
||||
|
||||
export const preparedInputFixture = (path: string): PreparedInputFixture | null => {
|
||||
const name = path.split(/[\\/]/).at(-1) ?? path;
|
||||
const markdown = preparedInputFixtures[name];
|
||||
return markdown ? { name, markdown } : null;
|
||||
};
|
||||
@@ -933,22 +933,41 @@
|
||||
gap: 0.4rem;
|
||||
min-height: 0;
|
||||
margin: 0;
|
||||
padding: 0.7rem 0;
|
||||
padding: 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__file button {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
gap: 0.3rem 0.55rem;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding: 0.58rem 0.62rem;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 0.5rem;
|
||||
color: var(--text-primary);
|
||||
text-align: left;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.run-input-file-browser__file button:hover,
|
||||
.run-input-file-browser__file button:focus-visible {
|
||||
border-color: color-mix(in oklch, var(--accent-cyan) 45%, var(--stage-line));
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.run-input-file-browser__file button[data-selected="true"] {
|
||||
border-color: color-mix(in oklch, var(--accent-cyan) 62%, var(--stage-line));
|
||||
background: color-mix(in oklch, var(--accent-cyan) 11%, oklch(0.13 0.025 250));
|
||||
}
|
||||
|
||||
.run-input-file-browser__icon {
|
||||
@@ -968,6 +987,55 @@
|
||||
.run-input-file-browser__marker {
|
||||
color: var(--accent-cyan);
|
||||
white-space: nowrap;
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.run-input-file-browser__body {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(12rem, 0.72fr) minmax(0, 1.28fr);
|
||||
gap: 0.75rem;
|
||||
min-height: 0;
|
||||
padding: 0.7rem 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.run-input-file-browser__preview {
|
||||
display: grid;
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
border-left: 1px solid var(--stage-line);
|
||||
padding-left: 0.75rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.run-input-file-browser__preview header {
|
||||
display: grid;
|
||||
gap: 0.18rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.run-input-file-browser__preview header code {
|
||||
color: var(--text-primary);
|
||||
font: 700 0.78rem/1.25 var(--font-mono, monospace);
|
||||
}
|
||||
|
||||
.run-input-file-browser__preview header span {
|
||||
color: var(--text-muted);
|
||||
font: 600 0.58rem/1.3 var(--font-mono, monospace);
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.run-input-file-browser__preview pre,
|
||||
.run-input-file-browser__preview p {
|
||||
min-height: 0;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
color: var(--text-primary);
|
||||
font: 500 0.72rem/1.5 var(--font-mono, monospace);
|
||||
white-space: pre-wrap;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
.run-input-file-browser__destination {
|
||||
@@ -976,12 +1044,8 @@
|
||||
}
|
||||
|
||||
@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;
|
||||
.run-input-file-browser__body {
|
||||
grid-template-columns: minmax(10rem, 0.85fr) minmax(0, 1.15fr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user