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);
|
Story-flow audit: [`presentation story audit`](runbooks/presentation-story-audit.md);
|
||||||
dated evidence: [`presentation rehearsal log`](runbooks/presentation-rehearsal-log.md).
|
dated evidence: [`presentation rehearsal log`](runbooks/presentation-rehearsal-log.md).
|
||||||
|
|
||||||
9. **Deferred: factual input file browser:** replace the Scene 10 input
|
9. **Completed: factual input file browser:** Scene 10 presents the run-selected
|
||||||
manifest-only view with a read-only browser backed by canonical prepared or
|
documents as a read-only browser with selectable prepared-fixture Markdown
|
||||||
live run facts. Distinguish declared, selected, read, and produced files;
|
excerpts and a separately labelled output destination. The preview states
|
||||||
do not imply file reads without evidence.
|
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
|
10. **Completed: presentation follow-up visual/story pass:** clarified Scene 1
|
||||||
and the lifecycle-to-authoring narrative, gave Scenes 7 and 9 a stronger
|
and the lifecycle-to-authoring narrative, gave Scenes 7 and 9 a stronger
|
||||||
|
|||||||
@@ -116,8 +116,8 @@ describe("DemoWorkflowScene", () => {
|
|||||||
renderBeat("input");
|
renderBeat("input");
|
||||||
|
|
||||||
const browser = screen.getByRole("region", { name: /workflow input files/i });
|
const browser = screen.getByRole("region", { name: /workflow input files/i });
|
||||||
expect(within(browser).getByText("project-brief.md")).toBeInTheDocument();
|
expect(within(browser).getByRole("button", { name: /project-brief\.md/i })).toBeInTheDocument();
|
||||||
expect(within(browser).getByText("architecture-notes.md")).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(within(browser).getByRole("group", { name: /workflow output/i })).toHaveTextContent("issue-board.json");
|
||||||
expect(screen.queryByRole("region", { name: /workflow input summary/i })).not.toBeInTheDocument();
|
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 });
|
const footer = screen.getByRole("contentinfo", { name: /presentation footer/i });
|
||||||
expect(within(footer).getByText("6 / 14")).toBeInTheDocument();
|
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();
|
expect(within(footer).getByText(/workflow\.runs\.trace/)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,23 @@
|
|||||||
import { cleanup, render, screen, within } from "@testing-library/react";
|
import { cleanup, render, screen, within } from "@testing-library/react";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
import { afterEach, describe, expect, it } from "vitest";
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
import { RunInputFileBrowser } from "./RunInputFileBrowser.js";
|
import { RunInputFileBrowser } from "./RunInputFileBrowser.js";
|
||||||
|
|
||||||
afterEach(() => cleanup());
|
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", () => {
|
describe("RunInputFileBrowser", () => {
|
||||||
it("renders included docs paths as factual prepared-run files", () => {
|
it("renders selected docs as an interactive file list", () => {
|
||||||
render(
|
render(
|
||||||
<RunInputFileBrowser
|
<RunInputFileBrowser
|
||||||
selectedDocuments={["docs/project-brief.md", "docs/architecture-notes.md"]}
|
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 });
|
const files = within(browser).getByRole("list", { name: /included in prepared run/i });
|
||||||
expect(within(files).getAllByRole("listitem")).toHaveLength(2);
|
expect(within(files).getAllByRole("listitem")).toHaveLength(2);
|
||||||
expect(within(files).getAllByText(/included in prepared run/i)).toHaveLength(2);
|
expect(within(files).getAllByText("selected")).toHaveLength(2);
|
||||||
expect(within(files).queryAllByRole("button")).toHaveLength(0);
|
expect(within(files).getAllByRole("button")).toHaveLength(2);
|
||||||
expect(within(files).queryAllByRole("link")).toHaveLength(0);
|
expect(within(files).queryAllByRole("link")).toHaveLength(0);
|
||||||
for (const path of ["docs/project-brief.md", "docs/architecture-notes.md"]) {
|
for (const path of ["docs/project-brief.md", "docs/architecture-notes.md"]) {
|
||||||
const row = within(files).getByText(path).closest("li");
|
const row = within(files).getByText(path).closest("li");
|
||||||
expect(row).not.toBeNull();
|
expect(row).not.toBeNull();
|
||||||
expect(row).toHaveAttribute("data-file-path", path);
|
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 });
|
const destination = within(browser).getByRole("group", { name: /workflow output/i });
|
||||||
expect(destination).toHaveTextContent("artifacts/issue-board.json");
|
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(
|
render(
|
||||||
<RunInputFileBrowser
|
<RunInputFileBrowser
|
||||||
selectedDocuments={["docs/project-brief.md"]}
|
selectedDocuments={["live-only.md"]}
|
||||||
boardPath="issue-board.json"
|
boardPath="issue-board.json"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
const browser = screen.getByRole("region", { name: /workflow input files/i });
|
expect(screen.getByRole("region", { name: /prepared fixture preview/i })).toHaveTextContent(
|
||||||
expect(within(browser).queryByText(/\b\d+(?:\.\d+)?\s*(?:kb|mb|bytes)\b/i)).not.toBeInTheDocument();
|
/preview unavailable/i,
|
||||||
expect(within(browser).queryByText(/preview|modified|created|content/i)).not.toBeInTheDocument();
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { preparedInputFixture } from "./run-input-fixtures.js";
|
||||||
|
|
||||||
export type RunInputFileBrowserProps = {
|
export type RunInputFileBrowserProps = {
|
||||||
readonly selectedDocuments: ReadonlyArray<string>;
|
readonly selectedDocuments: ReadonlyArray<string>;
|
||||||
readonly boardPath: string;
|
readonly boardPath: string;
|
||||||
@@ -6,24 +9,48 @@ export type RunInputFileBrowserProps = {
|
|||||||
export const RunInputFileBrowser = ({
|
export const RunInputFileBrowser = ({
|
||||||
selectedDocuments,
|
selectedDocuments,
|
||||||
boardPath,
|
boardPath,
|
||||||
}: RunInputFileBrowserProps) => (
|
}: RunInputFileBrowserProps) => {
|
||||||
<section className="run-input-file-browser" role="region" aria-label="workflow input files">
|
const [selectedPath, setSelectedPath] = useState(selectedDocuments[0] ?? "");
|
||||||
<header className="run-input-file-browser__header">
|
const visibleSelectedPath = selectedDocuments.includes(selectedPath)
|
||||||
<h3>docs/</h3>
|
? selectedPath
|
||||||
<span>included in prepared run</span>
|
: selectedDocuments[0] ?? "";
|
||||||
</header>
|
const fixture = preparedInputFixture(visibleSelectedPath);
|
||||||
<ul className="run-input-file-browser__list" aria-label="included in prepared run">
|
|
||||||
{selectedDocuments.map((path) => (
|
return (
|
||||||
<li className="run-input-file-browser__file" data-file-path={path} key={path}>
|
<section className="run-input-file-browser" aria-label="workflow input files">
|
||||||
<span className="run-input-file-browser__icon" aria-hidden="true">file</span>
|
<header className="run-input-file-browser__header">
|
||||||
<code>{path}</code>
|
<h3>docs/</h3>
|
||||||
<span className="run-input-file-browser__marker">included in prepared run</span>
|
<span>{selectedDocuments.length} selected by run input</span>
|
||||||
</li>
|
</header>
|
||||||
))}
|
<div className="run-input-file-browser__body">
|
||||||
</ul>
|
<ul className="run-input-file-browser__list" aria-label="included in prepared run">
|
||||||
<div className="run-input-file-browser__destination" role="group" aria-label="workflow output">
|
{selectedDocuments.map((path) => (
|
||||||
<span>workflow output</span>
|
<li className="run-input-file-browser__file" data-file-path={path} key={path}>
|
||||||
<code>{boardPath}</code>
|
<button
|
||||||
</div>
|
type="button"
|
||||||
</section>
|
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} />);
|
render(<SceneProgress location={location} />);
|
||||||
|
|
||||||
expect(screen.getByText("6 / 14")).toBeInTheDocument();
|
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", () => {
|
it("shows scene position for run-from-deployment/graph", () => {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const expectedBeats = {
|
|||||||
positioning: ["landscape", "lda-position"],
|
positioning: ["landscape", "lda-position"],
|
||||||
"planner-runtime": ["planner", "runtime", "boundary"],
|
"planner-runtime": ["planner", "runtime", "boundary"],
|
||||||
lifecycle: ["draft", "artifact", "deployment", "run"],
|
lifecycle: ["draft", "artifact", "deployment", "run"],
|
||||||
architecture: ["client", "api", "runtime", "node-use"],
|
architecture: ["overview", "client", "api", "runtime", "node-use"],
|
||||||
authoring: ["discover", "author", "diagnose", "repair"],
|
authoring: ["discover", "author", "diagnose", "repair"],
|
||||||
"agent-handoff": ["request"],
|
"agent-handoff": ["request"],
|
||||||
"prepared-lifecycle": ["discover", "draft", "validate", "artifact", "deployment"],
|
"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;
|
gap: 0.4rem;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0.7rem 0;
|
padding: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
}
|
}
|
||||||
|
|
||||||
.run-input-file-browser__file {
|
.run-input-file-browser__file {
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
||||||
gap: 0.55rem;
|
|
||||||
align-items: center;
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding: 0.5rem 0.55rem;
|
}
|
||||||
border: 1px solid color-mix(in oklch, var(--stage-line) 72%, transparent);
|
|
||||||
border-radius: 0.55rem;
|
.run-input-file-browser__file button {
|
||||||
background: oklch(0.13 0.025 250 / 0.72);
|
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 {
|
.run-input-file-browser__icon {
|
||||||
@@ -968,6 +987,55 @@
|
|||||||
.run-input-file-browser__marker {
|
.run-input-file-browser__marker {
|
||||||
color: var(--accent-cyan);
|
color: var(--accent-cyan);
|
||||||
white-space: nowrap;
|
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 {
|
.run-input-file-browser__destination {
|
||||||
@@ -976,12 +1044,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@container presentation-canvas (max-width: 620px) {
|
@container presentation-canvas (max-width: 620px) {
|
||||||
.run-input-file-browser__file {
|
.run-input-file-browser__body {
|
||||||
grid-template-columns: auto minmax(0, 1fr);
|
grid-template-columns: minmax(10rem, 0.85fr) minmax(0, 1.15fr);
|
||||||
}
|
|
||||||
|
|
||||||
.run-input-file-browser__marker {
|
|
||||||
grid-column: 2;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user