feat: add prepared authoring assistant pane
This commit is contained in:
@@ -0,0 +1,32 @@
|
|||||||
|
import { cleanup, render, screen } from "@testing-library/react";
|
||||||
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
|
import { PresentationAssistantPane } from "./PresentationAssistantPane.js";
|
||||||
|
|
||||||
|
afterEach(cleanup);
|
||||||
|
|
||||||
|
describe("PresentationAssistantPane", () => {
|
||||||
|
it("renders a persistent prepared replay surface for the current phase", () => {
|
||||||
|
render(<PresentationAssistantPane phase="validate" />);
|
||||||
|
|
||||||
|
expect(screen.getByRole("complementary", { name: /prepared authoring assistant/i })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole("heading", { name: /authoring assistant/i })).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/current phase: validate/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(/prepared replay only/i)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps the active tool group synchronized with the phase", () => {
|
||||||
|
render(<PresentationAssistantPane phase="artifact" />);
|
||||||
|
|
||||||
|
expect(screen.getByRole("button", { name: /artifact.*3 tool calls/i }))
|
||||||
|
.toHaveAttribute("aria-expanded", "true");
|
||||||
|
expect(screen.getByRole("button", { name: /validate.*2 tool calls/i }))
|
||||||
|
.toHaveAttribute("aria-expanded", "false");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not expose a live or run action", () => {
|
||||||
|
render(<PresentationAssistantPane phase="deployment" />);
|
||||||
|
|
||||||
|
expect(screen.queryByRole("button", { name: /run|send|execute/i })).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(/workflow\.runs\.start/i)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { AuthoringConversation } from "./AuthoringConversation.js";
|
||||||
|
import type { AuthoringPhaseId } from "./authoring-recording.js";
|
||||||
|
|
||||||
|
type PresentationAssistantPaneProps = {
|
||||||
|
readonly phase: AuthoringPhaseId;
|
||||||
|
};
|
||||||
|
|
||||||
|
const phaseLabels: Readonly<Record<AuthoringPhaseId, string>> = {
|
||||||
|
discover: "Discover",
|
||||||
|
draft: "Draft",
|
||||||
|
validate: "Validate",
|
||||||
|
artifact: "Artifact",
|
||||||
|
deployment: "Deployment",
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stable Scene 9 boundary for the prepared assistant surface.
|
||||||
|
*
|
||||||
|
* The pane deliberately passes no action handlers: its conversation is a
|
||||||
|
* replay projection, not a live assistant runtime or authoring client.
|
||||||
|
*/
|
||||||
|
export const PresentationAssistantPane = ({ phase }: PresentationAssistantPaneProps) => (
|
||||||
|
<aside
|
||||||
|
className="presentation-assistant-pane"
|
||||||
|
aria-label="prepared authoring assistant"
|
||||||
|
data-phase={phase}
|
||||||
|
data-surface="prepared-replay"
|
||||||
|
>
|
||||||
|
<header className="presentation-assistant-pane__header">
|
||||||
|
<p className="presentation-assistant-pane__eyebrow">Prepared workflow</p>
|
||||||
|
<h2>Authoring assistant</h2>
|
||||||
|
<p>Current phase: {phaseLabels[phase]}</p>
|
||||||
|
<p className="presentation-assistant-pane__disclosure">
|
||||||
|
Prepared replay only. No live actions or RPC calls.
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<div className="presentation-assistant-pane__conversation">
|
||||||
|
<AuthoringConversation
|
||||||
|
throughPhase={phase}
|
||||||
|
activePhase={phase}
|
||||||
|
surface="dock"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
@@ -3256,6 +3256,58 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.presentation-assistant-pane {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--stage-line);
|
||||||
|
border-radius: 0.65rem;
|
||||||
|
background: var(--stage-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-assistant-pane__header {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
padding: 0.85rem 0.9rem 0.7rem;
|
||||||
|
border-bottom: 1px solid var(--stage-line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-assistant-pane__header h2,
|
||||||
|
.presentation-assistant-pane__header p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-assistant-pane__header h2 {
|
||||||
|
font: 600 1rem/1.2 var(--font-interface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-assistant-pane__header > p:not(.presentation-assistant-pane__eyebrow):not(.presentation-assistant-pane__disclosure) {
|
||||||
|
margin-top: 0.35rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-assistant-pane__eyebrow {
|
||||||
|
margin-bottom: 0.3rem !important;
|
||||||
|
color: var(--accent-cyan);
|
||||||
|
font: 600 0.62rem/1 var(--font-mono);
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-assistant-pane__disclosure {
|
||||||
|
margin-top: 0.65rem !important;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.68rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-assistant-pane__conversation {
|
||||||
|
min-height: 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.prepared-lifecycle-scene[data-presentation-surface="editorial"] {
|
.prepared-lifecycle-scene[data-presentation-surface="editorial"] {
|
||||||
--authoring-paper: var(--color-editorial-paper, oklch(0.975 0.012 82));
|
--authoring-paper: var(--color-editorial-paper, oklch(0.975 0.012 82));
|
||||||
--authoring-ink: var(--color-editorial-ink, oklch(0.19 0.015 65));
|
--authoring-ink: var(--color-editorial-ink, oklch(0.19 0.015 65));
|
||||||
|
|||||||
@@ -509,3 +509,26 @@ tbody tr:nth-child(10) { animation-delay: 270ms; }
|
|||||||
display: block;
|
display: block;
|
||||||
color: var(--color-slate);
|
color: var(--color-slate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@custom-variant data-open (&:where([data-state="open"], [data-open]:not([data-open="false"])));
|
||||||
|
|
||||||
|
@custom-variant data-closed (&:where([data-state="closed"], [data-closed]:not([data-closed="false"])));
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
@keyframes collapsible-down {
|
||||||
|
from {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
height: var(--radix-collapsible-content-height, var(--collapsible-panel-height, auto));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes collapsible-up {
|
||||||
|
from {
|
||||||
|
height: var(--radix-collapsible-content-height, var(--collapsible-panel-height, auto));
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user