Fix wave chunk B: shared MenuShell for model menus, setup CTA on /chat/new

- components/menu-shell.tsx: one portaled, fixed-anchored, viewport-capped
  (60vh pattern) menu shell with site-preferred side + honest-fit auto-flip;
  computeMenuPlacement exported pure, placement-only (sites keep their own
  open/close/Esc contracts); re-places on capture-phase scroll/resize
- migrated: settings slot menus (agent-section), keys Add-provider,
  composer model+thinking (open up, right-aligned), SetupDialog pickers
  (portaled out of the overflow-hidden dialog frame — no clipping by
  construction); graph popovers + stats-bar summary stay bespoke (owner)
- /chat/new: the setup CTA above the draft blob, mirroring home's
- tests: menu-shell-pure (geometry/cap/flip/alignment), DraftShell CTA
This commit is contained in:
amosblomqvist
2026-09-22 16:56:44 +03:00
parent f6626f1703
commit 8675b19bef
8 changed files with 370 additions and 89 deletions
+28 -1
View File
@@ -74,8 +74,11 @@ describe("DraftShell — the sessionless surface", () => {
);
// The chat composer, ready for the first message.
assert.ok(html.includes('data-testid="composer"'));
// The pre-chat model control (its empty-chatId defer branch lifts here).
// The pre-chat model control (fix wave: the strong-slot control —
// app-wide, no per-chat write, no empty-chatId defer).
assert.ok(html.includes('data-testid="stats-model"'));
// No CTA by default: a configured surface stays quiet.
assert.ok(!html.includes('data-testid="setup-cta"'));
// The same empty state a fresh chat shows.
assert.ok(html.includes("The teacher will open by probing what you already know."));
// The pointer-events pattern (953b22b): the wrapper stays inert…
@@ -84,6 +87,30 @@ describe("DraftShell — the sessionless surface", () => {
assert.ok(/rounded-2xl[^>]*pointer-events-auto|pointer-events-auto[^>]*rounded-2xl/.test(html));
});
it("renders the setup CTA above the composer blob when passed (fix wave chunk B: /chat/new mirrors home)", () => {
const html = renderToStaticMarkup(
<DraftShell
busy={false}
text=""
onChange={() => {}}
onSend={() => {}}
settings={undefined}
onSettingsSaved={() => {}}
setupCta={
<button type="button" data-testid="setup-cta">
Set up your models — chats start after the three picks
</button>
}
/>,
);
// The same affordance home carries, above the draft shell's blob.
assert.ok(html.includes('data-testid="setup-cta"'));
// Above the composer: the CTA's markup precedes the blob's.
const cta = html.indexOf('data-testid="setup-cta"');
const blob = html.search(/rounded-2xl[^>]*pointer-events-auto|pointer-events-auto[^>]*rounded-2xl/);
assert.ok(cta !== -1 && blob !== -1 && cta < blob, "CTA above the blob");
});
it("renders the knowledge-context slider in the stats bar's left slot, at the decided default (absent settings = on, 1024)", () => {
const html = renderToStaticMarkup(
<DraftShell