From d2e33c5eed09afe3e226e59a027c978643bd73e9 Mon Sep 17 00:00:00 2001 From: Dave Date: Thu, 1 Oct 2026 00:12:56 +0200 Subject: [PATCH] fix(giellie): mobile overlay, dvh height, markdown image block, per-chat trust, keep draft on failure Co-Authored-By: Claude Sonnet 5.5 --- src/app/(app)/giellie/page.test.tsx | 61 +++++++++ src/app/(app)/giellie/page.tsx | 61 ++++++--- src/components/giellie/chat-list.tsx | 9 +- src/components/giellie/composer.test.tsx | 28 ++++ src/components/giellie/composer.tsx | 12 +- src/components/giellie/message-list.tsx | 4 +- src/components/giellie/use-giellie.test.tsx | 143 ++++++++++++++++++++ src/components/giellie/use-giellie.ts | 44 ++++-- src/components/markdown.test.tsx | 19 +++ src/components/markdown.tsx | 23 +++- 10 files changed, 366 insertions(+), 38 deletions(-) create mode 100644 src/app/(app)/giellie/page.test.tsx create mode 100644 src/components/giellie/composer.test.tsx create mode 100644 src/components/giellie/use-giellie.test.tsx create mode 100644 src/components/markdown.test.tsx diff --git a/src/app/(app)/giellie/page.test.tsx b/src/app/(app)/giellie/page.test.tsx new file mode 100644 index 0000000..fb91b8e --- /dev/null +++ b/src/app/(app)/giellie/page.test.tsx @@ -0,0 +1,61 @@ +import { describe, it, expect, vi } from "vitest"; +import { render, screen, fireEvent } from "@testing-library/react"; + +const newChat = vi.fn(); +const openChat = vi.fn(); + +vi.mock("@/components/giellie/use-giellie", () => ({ + useGielie: () => ({ + ready: { ready: true }, + chats: [ + { id: "a", title: "Chat A", updated: "" }, + { id: "b", title: "Chat B", updated: "" }, + ], + chat: { id: "a", title: "Chat A", updated: "", messages: [], pending_action: null, attachments: [] }, + liveText: "", + activity: [], + pending: null, + busy: false, + error: "", + openChat, + newChat, + send: vi.fn(), + decide: vi.fn(), + }), +})); + +import GielliePage from "./page"; + +const toggle = () => screen.getByRole("switch", { name: /trust this chat/i }); + +describe("GielliePage trust toggle", () => { + it("shows the email caveat as visible text", () => { + render(); + expect(screen.getByText(/emails always ask/i)).toBeTruthy(); + }); + + it("resets trust when another chat is opened", () => { + render(); + fireEvent.click(toggle()); + expect(toggle().getAttribute("aria-checked")).toBe("true"); + fireEvent.click(screen.getByText("Chat B")); + expect(openChat).toHaveBeenCalledWith("b"); + expect(toggle().getAttribute("aria-checked")).toBe("false"); + }); + + it("resets trust on New chat", () => { + render(); + fireEvent.click(toggle()); + fireEvent.click(screen.getByRole("button", { name: /new chat/i })); + expect(newChat).toHaveBeenCalled(); + expect(toggle().getAttribute("aria-checked")).toBe("false"); + }); + + it("closes the mobile chat list with the close button", () => { + render(); + fireEvent.click(screen.getByRole("button", { name: "Chats" })); + expect(screen.getByRole("button", { name: /close chats/i })).toBeTruthy(); + fireEvent.click(screen.getByRole("button", { name: /close chats/i })); + expect(screen.queryByRole("button", { name: /close chats/i })).toBeNull(); + }); +}); diff --git a/src/app/(app)/giellie/page.tsx b/src/app/(app)/giellie/page.tsx index 123bc31..bc98076 100644 --- a/src/app/(app)/giellie/page.tsx +++ b/src/app/(app)/giellie/page.tsx @@ -2,7 +2,7 @@ import Link from "next/link"; import { useState } from "react"; -import { Bot, Menu } from "lucide-react"; +import { Bot, Menu, X } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; import { ChatList } from "@/components/giellie/chat-list"; @@ -18,24 +18,47 @@ export default function GielliePage() { const [showList, setShowList] = useState(false); const notReady = g.ready && !g.ready.ready; + const errorLine = g.error ?

{g.error}

: null; return ( -
+ // Height = viewport minus the (app) layout chrome around {children}: + // below lg: 2px brand stripe + 56px (pt-14 mobile top bar) + 20px + 20px (p-5) = 98px + // lg+: 2px brand stripe + 32px + 32px (p-8) = 66px + // dvh (not vh) so the iOS Safari toolbar doesn't push the composer off-screen. +
+ {showList && ( +
setShowList(false)} + /> + )}