feat(FN-3222): add advanced planning disclosure layout, styling, and contro
This merge lands v0.16.0 with several major features: an advanced planning mode modal redesign with collapsible disclosure controls (FN-3222), a durable insight lifecycle system using bounded run executors with quick recovery (FN-3243/FN-3013), and per-project SQLite connection caching for AgentStor Fusion-Task-Id: FN-3222
This commit is contained in:
@@ -411,35 +411,55 @@
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.planning-advanced-blurb {
|
||||
margin: 0;
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
.planning-advanced-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-md);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.planning-model-select-group {
|
||||
.planning-advanced-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
max-width: 420px;
|
||||
margin: 0 auto;
|
||||
padding: var(--space-md);
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.planning-advanced-blurb {
|
||||
margin: 0;
|
||||
color: var(--text-muted);
|
||||
font-size: calc(var(--space-sm) + var(--space-xs));
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.planning-model-select-group {
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.planning-model-select-group .form-label {
|
||||
font-size: 13px;
|
||||
font-size: calc(var(--space-sm) + var(--space-xs) + var(--space-xs) * 0.25);
|
||||
color: var(--text-muted);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.planning-depth-selector {
|
||||
align-items: stretch;
|
||||
justify-content: flex-start;
|
||||
max-width: 100%;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.planning-depth-controls-row {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
gap: var(--space-md);
|
||||
max-width: 520px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.planning-depth-chip-group {
|
||||
@@ -1324,9 +1344,12 @@
|
||||
}
|
||||
|
||||
.planning-depth-selector {
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.planning-depth-controls-row {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.planning-depth-chip-group {
|
||||
|
||||
@@ -1557,11 +1557,8 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
||||
</div>
|
||||
|
||||
<OnboardingDisclosure summary="Advanced planning settings" className="planning-advanced-disclosure" defaultOpen>
|
||||
<p className="planning-advanced-blurb">
|
||||
Choose the planning model and tune plan depth to control how detailed the AI interview should be.
|
||||
</p>
|
||||
|
||||
<div className="planning-model-select-group">
|
||||
<div className="planning-advanced-content">
|
||||
<div className="planning-advanced-section planning-model-select-group">
|
||||
<label htmlFor="planning-modal-model" className="form-label">
|
||||
Planning Model
|
||||
{modelsLoading && (
|
||||
@@ -1570,6 +1567,9 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
||||
</span>
|
||||
)}
|
||||
</label>
|
||||
<p className="planning-advanced-blurb">
|
||||
Selects which model runs the planning interview and writes the final draft.
|
||||
</p>
|
||||
<CustomModelDropdown
|
||||
id="planning-modal-model"
|
||||
label="Planning Model"
|
||||
@@ -1613,38 +1613,44 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, onTasksCreat
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="planning-depth-selector">
|
||||
<div className="planning-depth-chip-group" role="group" aria-label="Planning depth">
|
||||
{([
|
||||
{ value: "small", label: "Small" },
|
||||
{ value: "medium", label: "Medium" },
|
||||
{ value: "large", label: "Large" },
|
||||
] as const).map((depthOption) => (
|
||||
<button
|
||||
key={depthOption.value}
|
||||
type="button"
|
||||
className={`planning-depth-chip btn ${planningDepth === depthOption.value ? "btn-primary planning-depth-chip-active" : ""}`}
|
||||
onClick={() => setPlanningDepth(depthOption.value)}
|
||||
aria-pressed={planningDepth === depthOption.value}
|
||||
>
|
||||
{depthOption.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="planning-advanced-section planning-depth-selector">
|
||||
<p className="planning-advanced-blurb">
|
||||
Plan size sets default interview depth. Questions lets you override with an exact count.
|
||||
</p>
|
||||
<div className="planning-depth-controls-row">
|
||||
<div className="planning-depth-chip-group" role="group" aria-label="Planning depth">
|
||||
{([
|
||||
{ value: "small", label: "Small" },
|
||||
{ value: "medium", label: "Medium" },
|
||||
{ value: "large", label: "Large" },
|
||||
] as const).map((depthOption) => (
|
||||
<button
|
||||
key={depthOption.value}
|
||||
type="button"
|
||||
className={`planning-depth-chip btn ${planningDepth === depthOption.value ? "btn-primary planning-depth-chip-active" : ""}`}
|
||||
onClick={() => setPlanningDepth(depthOption.value)}
|
||||
aria-pressed={planningDepth === depthOption.value}
|
||||
>
|
||||
{depthOption.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<label className="planning-depth-question-count" htmlFor="planning-depth-questions">
|
||||
<span>Questions</span>
|
||||
<input
|
||||
id="planning-depth-questions"
|
||||
className="input planning-depth-question-input"
|
||||
type="number"
|
||||
min={1}
|
||||
max={20}
|
||||
value={customQuestionCount}
|
||||
onChange={(e) => setCustomQuestionCount(e.target.value)}
|
||||
placeholder="Auto"
|
||||
/>
|
||||
</label>
|
||||
<label className="planning-depth-question-count" htmlFor="planning-depth-questions">
|
||||
<span>Questions</span>
|
||||
<input
|
||||
id="planning-depth-questions"
|
||||
className="input planning-depth-question-input"
|
||||
type="number"
|
||||
min={1}
|
||||
max={20}
|
||||
value={customQuestionCount}
|
||||
onChange={(e) => setCustomQuestionCount(e.target.value)}
|
||||
placeholder="Auto"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</OnboardingDisclosure>
|
||||
</div>
|
||||
|
||||
@@ -399,7 +399,7 @@ describe("PlanningModeModal", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("renders planning depth controls with medium selected by default", () => {
|
||||
it("renders advanced disclosure controls in the initial view", () => {
|
||||
render(
|
||||
<PlanningModeModal
|
||||
isOpen={true}
|
||||
@@ -410,12 +410,21 @@ describe("PlanningModeModal", () => {
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByRole("button", { name: "Advanced planning settings" })).toBeDefined();
|
||||
expect(screen.getByText(/Choose the planning model and tune plan depth/)).toBeDefined();
|
||||
expect(screen.getByRole("button", { name: "Small" })).toBeDefined();
|
||||
expect(screen.getByRole("button", { name: "Medium" }).getAttribute("aria-pressed")).toBe("true");
|
||||
expect(screen.getByRole("button", { name: "Large" })).toBeDefined();
|
||||
expect(screen.getByLabelText("Questions")).toBeDefined();
|
||||
const disclosureButton = screen.getByRole("button", { name: "Advanced planning settings" });
|
||||
expect(disclosureButton).toBeDefined();
|
||||
|
||||
const disclosure = disclosureButton.closest(".onboarding-disclosure");
|
||||
expect(disclosure).not.toBeNull();
|
||||
const disclosureScope = within(disclosure as HTMLElement);
|
||||
|
||||
expect(disclosureScope.getByRole("button", { name: "Planning Model" })).toBeDefined();
|
||||
expect(disclosureScope.getByText("Using default")).toBeDefined();
|
||||
expect(disclosureScope.getByText(/Selects which model runs the planning interview/)).toBeDefined();
|
||||
expect(disclosureScope.getByText(/Plan size sets default interview depth/)).toBeDefined();
|
||||
expect(disclosureScope.getByRole("button", { name: "Small" })).toBeDefined();
|
||||
expect(disclosureScope.getByRole("button", { name: "Medium" }).getAttribute("aria-pressed")).toBe("true");
|
||||
expect(disclosureScope.getByRole("button", { name: "Large" })).toBeDefined();
|
||||
expect(disclosureScope.getByLabelText("Questions")).toBeDefined();
|
||||
});
|
||||
|
||||
it("updates selected depth and sends custom question count", async () => {
|
||||
|
||||
Reference in New Issue
Block a user