🚸 feat: redesign AI toolbar with two-zone layout and HoverCard shortcuts popover

- Split composer into AI zone (primary accent) and data actions zone (neutral)
- Move Attach/Generate to labeled footer bar below textarea (left/right aligned)
- Add info icon with HoverCard (hover preview) + Popover (pinned click) showing identical keyboard shortcuts content using shadcn HoverCard to fix theme inconsistency vs Tooltip
- Expose imperative triggerRef on ImagePicker for keyboard shortcut access
- Wire TooltipProvider in root layout; install shadcn kbd and hover-card
- Unauthenticated state shows locked CTA with real sign-in button weight
- Add behavioral contract tests for footer bar, info trigger, and zone layout
This commit is contained in:
2026-04-08 13:08:36 -04:00
parent 650d1d5f95
commit 722c0f0f7d
8 changed files with 881 additions and 151 deletions

View File

@@ -0,0 +1,137 @@
import { describe, expect, test } from "bun:test";
import {
SHORTCUT_DEFINITIONS,
resolveKeys,
} from "@/lib/keyboard-shortcuts";
// ---------------------------------------------------------------------------
// Keyboard shortcuts OS-aware key resolution
//
// Public interface under test: resolveKeys(modifiers, os) — a pure function
// that maps abstract modifier tokens to display glyphs based on the detected
// operating system.
//
// We test the pure function directly, no browser or DOM required.
// The React hook (useOs) is just a thin browser wrapper around the same
// detection logic and doesn't need separate unit tests.
// ---------------------------------------------------------------------------
describe("resolveKeys Mac", () => {
test("mod resolves to ⌘ on Mac", () => {
expect(resolveKeys(["mod"], "mac")).toEqual(["⌘"]);
});
test("shift resolves to ⇧ on Mac", () => {
expect(resolveKeys(["shift"], "mac")).toEqual(["⇧"]);
});
test("alt resolves to ⌥ on Mac", () => {
expect(resolveKeys(["alt"], "mac")).toEqual(["⌥"]);
});
test("enter resolves to ↵ on Mac", () => {
expect(resolveKeys(["enter"], "mac")).toEqual(["↵"]);
});
test("esc resolves to Esc on Mac", () => {
expect(resolveKeys(["esc"], "mac")).toEqual(["Esc"]);
});
test("combined mod+enter resolves correctly on Mac", () => {
expect(resolveKeys(["mod", "enter"], "mac")).toEqual(["⌘", "↵"]);
});
test("combined mod+shift+A resolves correctly on Mac", () => {
expect(resolveKeys(["mod", "shift", "A"], "mac")).toEqual(["⌘", "⇧", "A"]);
});
});
describe("resolveKeys Windows / Linux", () => {
test("mod resolves to Ctrl on non-Mac", () => {
expect(resolveKeys(["mod"], "other")).toEqual(["Ctrl"]);
});
test("shift resolves to Shift on non-Mac", () => {
expect(resolveKeys(["shift"], "other")).toEqual(["Shift"]);
});
test("alt resolves to Alt on non-Mac", () => {
expect(resolveKeys(["alt"], "other")).toEqual(["Alt"]);
});
test("enter resolves to Enter on non-Mac", () => {
expect(resolveKeys(["enter"], "other")).toEqual(["Enter"]);
});
test("esc resolves to Esc on non-Mac (same as Mac)", () => {
expect(resolveKeys(["esc"], "other")).toEqual(["Esc"]);
});
test("combined mod+enter resolves correctly on non-Mac", () => {
expect(resolveKeys(["mod", "enter"], "other")).toEqual(["Ctrl", "Enter"]);
});
test("combined mod+shift+A resolves correctly on non-Mac", () => {
expect(resolveKeys(["mod", "shift", "A"], "other")).toEqual(["Ctrl", "Shift", "A"]);
});
});
describe("resolveKeys unknown OS (SSR fallback)", () => {
test("unknown falls back to Mac glyphs (most users are Mac)", () => {
expect(resolveKeys(["mod"], "unknown")).toEqual(["⌘"]);
});
test("unknown enter fallback", () => {
expect(resolveKeys(["enter"], "unknown")).toEqual(["↵"]);
});
});
describe("resolveKeys passthrough for plain keys", () => {
test("plain letter A passes through unchanged on Mac", () => {
expect(resolveKeys(["A"], "mac")).toEqual(["A"]);
});
test("plain letter A passes through unchanged on non-Mac", () => {
expect(resolveKeys(["A"], "other")).toEqual(["A"]);
});
});
describe("SHORTCUT_DEFINITIONS schema contract", () => {
test("every definition has a non-empty modifiers array", () => {
for (const def of SHORTCUT_DEFINITIONS) {
expect(def.modifiers.length).toBeGreaterThan(0);
}
});
test("every definition has a non-empty label", () => {
for (const def of SHORTCUT_DEFINITIONS) {
expect(def.label.length).toBeGreaterThan(0);
}
});
test("Generate event shortcut exists and uses mod+enter", () => {
const gen = SHORTCUT_DEFINITIONS.find((d) =>
d.label.toLowerCase().includes("generate"),
);
expect(gen).toBeDefined();
expect(gen!.modifiers).toContain("mod");
expect(gen!.modifiers).toContain("enter");
});
test("Attach image shortcut exists and uses mod+shift", () => {
const attach = SHORTCUT_DEFINITIONS.find((d) =>
d.label.toLowerCase().includes("attach"),
);
expect(attach).toBeDefined();
expect(attach!.modifiers).toContain("mod");
expect(attach!.modifiers).toContain("shift");
});
test("Clear prompt shortcut exists and uses esc", () => {
const clear = SHORTCUT_DEFINITIONS.find((d) =>
d.label.toLowerCase().includes("clear"),
);
expect(clear).toBeDefined();
expect(clear!.modifiers).toContain("esc");
});
});