feat: expose factual run input browser

This commit is contained in:
lda
2026-07-13 10:56:55 +07:00 Verified
parent 78235d1f0e
commit f6b8edba87
9 changed files with 250 additions and 54 deletions
+5 -4
View File
@@ -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">
}: 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>included in prepared run</span>
<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}>
<span className="run-input-file-browser__icon" aria-hidden="true">file</span>
<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">included in prepared run</span>
<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>workflow output</span>
<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);
}
}