diff --git a/packages/web/src/hooks/use-overflowing.ts b/packages/web/src/hooks/use-overflowing.ts new file mode 100644 index 00000000..40056bc2 --- /dev/null +++ b/packages/web/src/hooks/use-overflowing.ts @@ -0,0 +1,26 @@ +import {useCallback, useState} from 'react'; + +/** + * Whether the element the ref is put on holds more than it shows, so it scrolls: measured as it or + * what is in it resizes. + */ +export function useOverflowing(): [(element: HTMLElement | null) => void, boolean] { + const [overflowing, setOverflowing] = useState(false); + const measure = useCallback((element: HTMLElement | null) => { + if (!element) { + return; + } + const read = () => setOverflowing(element.scrollHeight > element.clientHeight); + read(); + if (typeof ResizeObserver === 'undefined') { + return; + } + const observer = new ResizeObserver(read); + observer.observe(element); + for (const child of element.children) { + observer.observe(child); + } + return () => observer.disconnect(); + }, []); + return [measure, overflowing]; +} diff --git a/packages/web/src/index.css b/packages/web/src/index.css index 2d663785..73259587 100644 --- a/packages/web/src/index.css +++ b/packages/web/src/index.css @@ -1,5 +1,12 @@ @import '@proxy/ui/styles.css'; +/* The sidebars, the navigation and Chat's agents, take the page's background: their borders alone + set them apart. */ +:root, +.dark { + --sidebar: var(--background); +} + /* The macOS desktop window uses a hidden native title bar. Its drag surface covers the column seams below it while the native traffic lights remain above; browsers keep the page's normal edge-to-edge layout. */ diff --git a/packages/web/src/pages/chat-page.tsx b/packages/web/src/pages/chat-page.tsx index 5dfbfab7..90b8218f 100644 --- a/packages/web/src/pages/chat-page.tsx +++ b/packages/web/src/pages/chat-page.tsx @@ -42,6 +42,7 @@ import {preview} from '@proxy/utils/harness'; import {cn} from '@proxy/ui/lib/utils'; import {useHuman} from '@/hooks/use-human'; import {useOrgSlug} from '@/hooks/use-org'; +import {useOverflowing} from '@/hooks/use-overflowing'; import {useUnread} from '@/hooks/use-unread'; import {formatAgo, formatDateTime} from '@/lib/format'; import type {Pose} from '@/lib/head'; @@ -128,7 +129,7 @@ function lastOf(log: ThreadLog): ChatPersona['lastMessage'] { // The organization's personas in the second sidebar, each with its head, its name and the last // thing said between it and the person, marked where the person hasn't seen it, the open one lit, // each head watching the pointer while it is over that agent's row, and a row at the bottom to -// make a new one. +// make a new one, set off by a line only while the list scrolls under it. function PersonaList({ personas, current, @@ -157,12 +158,13 @@ function PersonaList({ const org = useOrgSlug(); const unread = useUnread(); const [hovered, setHovered] = useState(takeOpeningPersona); + const [measureList, listOverflows] = useOverflowing(); // Whether the persona said something the person hasn't seen, as the provider has it. const isUnread = (persona: ChatPersona) => persona.id !== current && unread.unreadIds.has(persona.id); return (
-
+
{personas.length === 0 && ( No agents yet. )} @@ -259,7 +261,7 @@ function PersonaList({ })}
-
+