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:
Fusion
2026-05-03 03:13:55 -07:00
committed by gsxdsm
parent a47f3192e8
commit eaae216527
3 changed files with 94 additions and 56 deletions

View File

@@ -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 {

View File

@@ -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>

View File

@@ -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 () => {