Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions packages/web/src/hooks/use-overflowing.ts
Original file line number Diff line number Diff line change
@@ -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];
}
7 changes: 7 additions & 0 deletions packages/web/src/index.css
Original file line number Diff line number Diff line change
@@ -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. */
Expand Down
8 changes: 5 additions & 3 deletions packages/web/src/pages/chat-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 (
<div className="flex h-full min-h-0 flex-col">
<div className="flex min-h-0 flex-1 flex-col overflow-y-auto p-2">
<div ref={measureList} className="flex min-h-0 flex-1 flex-col overflow-y-auto p-2">
{personas.length === 0 && (
<span className="px-2 py-1.5 text-sm text-muted-foreground">No agents yet.</span>
)}
Expand Down Expand Up @@ -259,7 +261,7 @@ function PersonaList({
})}
</ul>
</div>
<div className="shrink-0 border-t p-2">
<div className={cn('shrink-0 p-2', listOverflows && 'border-t')}>
<button
type="button"
onClick={onNew}
Expand Down
Loading