diff --git a/README.md b/README.md index c5dfeb6..5191581 100644 --- a/README.md +++ b/README.md @@ -1419,9 +1419,20 @@ Things I chose while building, with the reasoning: chips, input, chips, note — shares one left edge. In Aero the welcome panel is the same frosted glass as the cards (blurred, not a flat tint) and - stretches flush with the header and the composer (measured: 268..1280 at a 1280 window, exactly - the chrome's edges) — a flat pane floating 40px inside the scene read as "cut off next to the - sidebar". + stretches to the content column's edges — flush under the header and above the composer + (measured: 268..1280 at a 1280 window) — a flat pane floating 40px inside the scene read as + "cut off next to the sidebar". + +87. **Chat's chrome docks into the rail (the sidebar seam).** The chat header and composer are + full-bleed bands inside the main pane; at desktop widths that pane starts right at the sidebar, + so both bands ended in a hard vertical edge beside the floating rail card — the 12px gutter + showed the raw scene, and the bands' fill + backdrop blur made it read as a color step at the + seam, with their hairlines starting in mid-air at that edge. Both bands now reach the card's + right edge (the rail's own 12px padding, expanded or collapsed) and their hairlines are inset + to the content box instead of touching the seam; phones keep the plain full-width borders. The + chat suite pins the dock (band edge === card edge, zeroed band borders, inset `::after` + hairlines). The "Chat is set to ignore notes text" banner went away with it — the scope chip + already says what the chat may read. 86. **Reduced motion no longer ghosts the sheets.** The in-app toggle fed framer through `MotionConfig reducedMotion="always"`, which only strips *transform* animations — while every diff --git a/app/globals.css b/app/globals.css index 91659cb..f3b6b74 100644 --- a/app/globals.css +++ b/app/globals.css @@ -690,11 +690,15 @@ } /* Chat header: the same glass as the cards, a touch more fill — its title and - scope line sit directly over the sky. */ + scope line sit directly over the sky. At lg its divider is the inset hairline + the desktop docking block below draws (::after), so it never hits the seam. */ .aero .chat-header { background-color: rgb(232 248 255 / 0.4); border-bottom: 1px solid rgb(255 255 255 / 0.6); } +.aero .chat-header::after { + background: rgb(255 255 255 / 0.6); +} /* The chat composer: one flat frosted panel for the input, the chips and the note — instead of each of them floating on the wallpaper. */ @@ -703,6 +707,56 @@ border-top: 1px solid rgb(255 255 255 / 0.65); box-shadow: 0 -16px 36px -26px rgb(2 62 110 / 0.5); } +.aero .chat-composer::after { + background: rgb(255 255 255 / 0.65); +} + +/* Desktop: the chat chrome docks into the rail. The header and the composer + are full-bleed bands inside the main pane; at lg that pane starts exactly + at the sidebar, so both bands used to stop in a hard vertical edge beside + the floating rail card — the scene showed through the 12px gutter and the + bands' own fill/blur made it read as a color step at the seam. They now + reach the card's right edge (the rail's own 12px padding, in the expanded + and the collapsed state alike) and their hairlines span the content box + instead of starting in mid-air at the seam. */ +@media (min-width: 1024px) { + .chat-header { + margin-left: -0.75rem; + padding-left: 1.75rem; + border-bottom: 0; + } + .aero .chat-header { + border-bottom: 0; + } + .chat-header::after { + content: ""; + position: absolute; + left: 1.75rem; + right: 1rem; + bottom: 0; + height: 1px; + background: var(--glass-border); + } + .chat-composer { + margin-left: -0.75rem; + } + .chat-composer-band { + padding-left: 3.25rem; + border-top: 0; + } + .aero .chat-composer { + border-top: 0; + } + .chat-composer::after { + content: ""; + position: absolute; + left: 3.25rem; + right: 2.5rem; + top: 0; + height: 1px; + background: var(--glass-border); + } +} /* The chat welcome (subtitle + suggestions): the same frosted glass as the cards (the shared recipe above), stretched flush with the header and the diff --git a/components/chat/ChatScreen.tsx b/components/chat/ChatScreen.tsx index e606402..4bc6c33 100644 --- a/components/chat/ChatScreen.tsx +++ b/components/chat/ChatScreen.tsx @@ -3,7 +3,6 @@ import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import { useAppReducedMotion as useReducedMotion } from "@/lib/motion-pref"; -import Link from "next/link"; import { Composer, type PendingAttachment } from "./Composer"; import { ContextPicker } from "./ContextPicker"; @@ -769,14 +768,6 @@ export function ChatScreen() { /> - {settings.chatNotes === false && scope.kind !== "none" ? ( -

- {t("chat.notesOffNote")}{" "} - - {t("chat.notesOffLink")} - -

- ) : null} 0; return ( -
+
{attachments.length > 0 ? (
    {attachments.map((entry, index) => ( diff --git a/lib/i18n-dict.ts b/lib/i18n-dict.ts index 7f43f92..1994f2a 100644 --- a/lib/i18n-dict.ts +++ b/lib/i18n-dict.ts @@ -945,8 +945,6 @@ const en: Record = { "chat.emptyHint": "Answers come from your own decks. Pick what the chat may read with the context chip below.", "chat.oldDecksNote": "Older decks only have the generated study content. Re-add the notes for better answers.", "chat.fromLabel": "From", - "chat.notesOffNote": "Chat is set to ignore notes text.", - "chat.notesOffLink": "Turn it back on in Settings", "chat.attach": "Attach photos or documents", "chat.attachPhoto": "photo", "chat.attachDoc": "document", @@ -1931,8 +1929,6 @@ const cs: Record = { "chat.emptyHint": "Odpovědi vycházejí z tvých sad. Pomocí chipu s kontextem níže vyber, co smí chat číst.", "chat.oldDecksNote": "Starší sady mají jen vygenerovaný obsah. Přidej znovu poznámky a odpovědi budou lepší.", "chat.fromLabel": "Z", - "chat.notesOffNote": "Chat má vypnuté používání textu poznámek.", - "chat.notesOffLink": "Zapnout v nastavení", "chat.attach": "Přiložit fotky nebo dokumenty", "chat.attachPhoto": "fotka", "chat.attachDoc": "dokument", diff --git a/package.json b/package.json index 2af0b03..7cb5621 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "freezmo", - "version": "0.5.4", + "version": "0.5.5", "private": true, "license": "GPL-3.0-only", "scripts": { diff --git a/public/sw.js b/public/sw.js index df32e11..77e5bc4 100644 --- a/public/sw.js +++ b/public/sw.js @@ -9,7 +9,7 @@ * * Bumping CACHE_VERSION is how a new deploy invalidates old assets. */ -const CACHE_VERSION = "freezmo-v76"; +const CACHE_VERSION = "freezmo-v77"; const SHELL_CACHE = `${CACHE_VERSION}-shell`; const ASSET_CACHE = `${CACHE_VERSION}-assets`; diff --git a/tests/chat-test.mjs b/tests/chat-test.mjs index 6a75b88..8e3591d 100644 --- a/tests/chat-test.mjs +++ b/tests/chat-test.mjs @@ -291,6 +291,58 @@ try { await desktop.page.waitForTimeout(400); check("desktop: AI disclaimer under the composer", await desktop.page.getByText("Freezmo uses AI and can make mistakes").isVisible()); + /* The chat chrome docks into the rail: the header and the composer used to + end in a hard vertical edge beside the floating rail card (the scene + showed through the gutter and their fill/blur made it read as a color + step at the seam), and their hairlines started right at that edge. Both + bands now reach the card's right edge and their hairlines are inset to + the content box. */ + const seam = await desktop.page.evaluate(() => { + const card = document.querySelector(".sidebar-surface"); + const header = document.querySelector(".chat-header"); + const band = document.querySelector(".chat-composer-band"); + if (!card || !header || !band) return null; + const line = (el) => { + const cs = getComputedStyle(el, "::after"); + return { content: cs.content, left: parseFloat(cs.left), right: parseFloat(cs.right) }; + }; + return { + cardRight: Math.round(card.getBoundingClientRect().right), + headerLeft: Math.round(header.getBoundingClientRect().left), + bandLeft: Math.round(band.getBoundingClientRect().left), + headerBorder: getComputedStyle(header).borderBottomWidth, + bandBorder: getComputedStyle(band).borderTopWidth, + headerLine: line(header), + bandLine: line(band.parentElement), + }; + }); + check( + "desktop: chat chrome docks into the rail (no seam)", + seam !== null && + seam.headerLeft === seam.cardRight && + seam.bandLeft === seam.cardRight && + seam.headerBorder === "0px" && + seam.bandBorder === "0px" && + seam.headerLine.content !== "none" && + seam.headerLine.left > 0 && + seam.bandLine.content !== "none" && + seam.bandLine.left > 0, + JSON.stringify(seam), + ); + + /* The notes-off banner is gone: with "keep notes text" off, chat no longer + nags at the bottom — the scope chip already says what it reads. */ + const quiet = await makePage(browser, { width: 1280, height: 900, deviceScaleFactor: 1 }); + await seed(quiet.page, { + decks: [DECK_NOTES], + extra: { "freezmo.settings.v1": JSON.stringify({ apiKey: "test-key", chatNotes: false }) }, + }); + await quiet.page.goto(`${APP}/chat`, { waitUntil: "networkidle" }); + await quiet.page.waitForTimeout(900); + const quietText = await quiet.page.evaluate(() => document.body.innerText); + check("desktop: no notes-off banner with chat notes off", !/ignore notes text|ignoruje/i.test(quietText)); + await quiet.context.close(); + /* 6b. desktop pickers: the chip that opens also closes --------------- (It used to only ever open, so the only way out was a choice.) */ const modelChip = () => desktop.page.getByRole("button", { name: /Stub (Text|Vision)|Chat model/ }).first();