Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
c7a6cb5
Split the publishing form into a provider, the publish action and the…
yattias Sep 22, 2026
ddac2b8
Replace the AI Mode document tabs with detail blocks and header publi…
yattias Sep 22, 2026
39ac97b
Share one chat session between the AI Mode overlay and the notebook p…
yattias Sep 22, 2026
51e69e0
Start a workspace conversation by saying whether you fund or need fun…
yattias Sep 22, 2026
ba427d9
Lift the notebook's note listing into a hook and name a note's kind
yattias Sep 22, 2026
66139d3
Break the AI Mode overlay into a shell, a sidebar and the panes
yattias Sep 22, 2026
37d9449
Open documents in the workspace, with a chat on each that is kept onc…
yattias Sep 22, 2026
a357fbb
Give phones a full-screen view of conversations and documents
yattias Sep 22, 2026
9f5fa54
Workspace review fixes: one-line sidebar rows, no layout toggle, no n…
yattias Sep 22, 2026
22d5b0c
Open the workspace from the nav and top bar, and name it Workspace
yattias Sep 22, 2026
fccfab2
Start screen: intent tabs on the composer, profile and RFP as chips
yattias Sep 22, 2026
e480a40
My Funding: totals in the rail, workspace composer under the tabs
yattias Sep 22, 2026
4fd1a3f
Enter the workspace from Publish and My Funding, not the nav
yattias Sep 22, 2026
c35755d
Workspace strip names what is open; funders get a concierge card
yattias Sep 22, 2026
0d69925
Draft rows carry their status; the journey timeline connects
yattias Sep 23, 2026
5946da1
My Funding lists RFPs and proposals as one kind of row
yattias Sep 23, 2026
b97acc2
My Funding rows: live work first, with its image and its money
yattias Sep 23, 2026
0bb84d8
Focus the composer on a new conversation; one form input per field
yattias Sep 23, 2026
110ab8b
RFP guidelines, workspace steps aside on publish, deletable drafts
yattias Sep 23, 2026
64b243e
Draft rows: delete lives behind a three-dots menu
yattias Sep 23, 2026
9bd7ff9
Start screen: words-only timeline, privacy line under the composer
yattias Sep 23, 2026
fd2c9bc
Start screen cards, "Let's get started", RFP hero tabs beside the pool
yattias Sep 23, 2026
957644f
RFP hero footnote breathes; pool contribution says what it does
yattias Sep 23, 2026
ded7aec
Pool modal: the note sits under the amount, with coins; no raised line
yattias Sep 23, 2026
53c639a
Revert the Publish menu and homepage tab icons to the originals
yattias Sep 23, 2026
3422208
Document pane: the drafting row says only "Drafting"
yattias Sep 23, 2026
5c96d8a
Workspace: the start screen is "New RFP" or "New proposal"
yattias Sep 23, 2026
7e59282
ESLint skips Playwright's output folders
yattias Oct 4, 2026
0e4b631
Activity wears a live dot: the RFP tab and the Recent Activity rail
yattias Oct 4, 2026
fd4fcf8
My Funding is one dashboard: Overview and Activity under a hero
yattias Oct 4, 2026
d5671a4
Workspace is a page at /workspace, not an overlay
yattias Oct 5, 2026
b26017d
The document heads with a byline, and Publish asks for the rest in a …
yattias Oct 6, 2026
31a4965
Documents live in the sidebar, and the workspace is chat first with H…
yattias Oct 6, 2026
d088fb1
A new draft's document is gray from the first message, not after a flash
yattias Oct 6, 2026
64b9467
Workspace feedback: hamburger menu, Publish hover, phone document and…
yattias Oct 7, 2026
5a50b50
Recently visited shows each document's picture and when you were there
yattias Oct 7, 2026
c0820e8
The sidebar footer is one "Help & resources" menu
yattias Oct 7, 2026
d6a7446
Mobile polish: top bar logo, slide-over menu, activity sentences, pub…
yattias Oct 7, 2026
d17fe39
New RFP examples and How it works; no model picker
yattias Oct 7, 2026
9e46ca1
One title: the document's heading, with details under it
yattias Oct 7, 2026
ce998b8
Tooltips: full draft titles in the sidebar, sketches for document det…
yattias Oct 7, 2026
6112046
Simpler chat and publish dialog: refusal help, boxed choices, compact…
yattias Oct 8, 2026
4a14d0a
Chat menu sits left of History
yattias Oct 8, 2026
08fa48c
The peer review page opens with a journal-style hero, How it works an…
yattias Oct 8, 2026
b47ef5a
My Funding builds itself from what the user does: a journal-style her…
yattias Oct 8, 2026
46e194e
Journal, Peer Review and My Funding heroes share one compact height
yattias Oct 8, 2026
059fcdf
Merge pull request #1156 from ResearchHub/heroes/compact
yattias Oct 8, 2026
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
11 changes: 3 additions & 8 deletions app/endowment/components/EndowmentTopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,9 @@ export function EndowmentTopBar() {
opacity 0.25s ease,
transform 0.25s ease;
}
/* Clear the app nav rail, which is 70px from the tablet breakpoint and
240px once it expands at sidebar-compact. Below tablet the rail is
off-canvas, so the bar spans the full width. */
@media (min-width: 768px) {
.endowment-topbar {
left: 70px;
}
}
/* Clear the app's left column, which is 240px from sidebar-compact
up. Narrower, the sidebar is off-canvas, so the bar spans the full
width. */
@media (min-width: 1240px) {
.endowment-topbar {
left: 240px;
Expand Down
8 changes: 4 additions & 4 deletions app/endowment/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ export default function EndowmentLayout({ children }: Readonly<{ children: React
return (
<div className="flex min-h-screen bg-white">
{/* The landing page keeps its own top bar and footer, so it stays outside
PageLayout; the nav rail is mounted directly to preserve continuity
with the rest of the app. Passing `isOpen={false}` leaves it
off-canvas below the tablet breakpoint, where there is no menu button
to reopen it. */}
PageLayout; the app's left column is mounted directly to preserve
continuity with the rest of the app. Passing `isOpen={false}` leaves
it off-canvas below 1240px, where this page has no menu button to
open it. */}
<LeftSidebarContainer isOpen={false} />
<main className="relative min-w-0 flex-1">{children}</main>
</div>
Expand Down
22 changes: 22 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,28 @@ body {
}
}

/* The workspace page's lists and panes arrive by opacity alone, while the
logo, the Publish button and the top bar around them hold still. Not
`.animate-in`, which also slides. */
.workspace-fade-in {
animation: workspaceFadeIn 0.2s ease-out;
}

@keyframes workspaceFadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

@media (prefers-reduced-motion: reduce) {
.workspace-fade-in {
animation: none;
}
}

/* Hide scrollbar for Chrome, Safari and Opera */
.scrollbar-hide::-webkit-scrollbar {
display: none;
Expand Down
9 changes: 5 additions & 4 deletions app/journal/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const PIPELINE_STEPS = [
] as const;

const JournalIssueCards = () => (
<div className="relative aspect-[3/4] w-[11.7rem] flex-shrink-0 sm:w-[13.5rem]">
<div className="relative aspect-[3/4] w-[11.7rem] flex-shrink-0">
<div
aria-hidden="true"
className="absolute -inset-6 origin-bottom -rotate-[6deg] rounded-[2.5rem] bg-primary-500/25 blur-2xl"
Expand Down Expand Up @@ -83,7 +83,7 @@ const JournalIssueCards = () => (
);

const JournalTimeline = () => (
<div className="mt-8 w-full max-w-lg overflow-x-auto pb-1">
<div className="mt-6 w-full max-w-lg overflow-x-auto pb-1">
<div className="grid grid-cols-2">
{PIPELINE_STEPS.map((step, index) => {
const isCurrent = step.href === null;
Expand Down Expand Up @@ -168,7 +168,7 @@ const MobileJournalDetails = () => (

const JournalHero = () => (
<div className="relative border-b border-gray-200 bg-gray-50">
<div className="relative z-10 mx-auto max-w-[1180px] px-4 py-16 tablet:!px-8 sm:py-20">
<div className="relative z-10 mx-auto max-w-[1180px] px-4 py-10 tablet:!px-8 sm:py-12">
<div className="flex flex-col gap-10 lg:flex-row lg:items-center lg:justify-between">
<div className="max-w-2xl">
<h1
Expand All @@ -187,7 +187,8 @@ const JournalHero = () => (
<JournalTimeline />
</div>

<div className="flex justify-center lg:justify-end lg:pr-6">
{/* Wide screens only, and no taller than the text beside it, as on My Funding. */}
<div className="hidden lg:flex lg:justify-end lg:pr-6">
<JournalIssueCards />
</div>
</div>
Expand Down
49 changes: 29 additions & 20 deletions app/layouts/LeftSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,26 @@

import { AlertCircle } from 'lucide-react';
import { usePathname } from 'next/navigation';
import { FooterLinks } from '../../components/FooterLinks';
import { Navigation } from './Navigation';
import toast from 'react-hot-toast';
import { PublishMenu } from './PublishMenu';
import { Logo } from '@/components/ui/Logo';
import Link from 'next/link';
import { Icon } from '@/components/ui/icons';
import { FundingPowerRailButton } from '@/components/Funding/FundingPowerRailButton';
import { SidebarDocuments } from './components/SidebarDocuments';
import { SidebarHelpMenu } from './components/SidebarHelpMenu';

interface LeftSidebarProps {
forceMinimize?: boolean;
/**
* Runs once something in it is picked: below 1240px the sidebar is a menu,
* and a pick that changes the page in place must still close it.
*/
onNavigate?: () => void;
}

export const LeftSidebar: React.FC<LeftSidebarProps> = ({ forceMinimize = false }) => {
export const LeftSidebar: React.FC<LeftSidebarProps> = ({ forceMinimize = false, onNavigate }) => {
const pathname = usePathname();

const handleUnimplementedFeature = (featureName: string) => {
Expand Down Expand Up @@ -43,41 +49,44 @@ export const LeftSidebar: React.FC<LeftSidebarProps> = ({ forceMinimize = false

return (
<div className={`h-full flex flex-col z-50 bg-white overflow-hidden ${minimizeClass}`}>
<div
className={`p-4 pl-4 ${forceMinimize ? '!flex !justify-center' : 'tablet:max-sidebar-compact:!flex tablet:max-sidebar-compact:!justify-center'} pt-[10px]`}
>
<Link href="/">
<div className={forceMinimize ? '!hidden' : 'tablet:max-sidebar-compact:!hidden ml-1'}>
<div className={`p-4 pl-4 ${forceMinimize ? '!flex !justify-center' : ''} pt-[10px]`}>
<Link href="/" onClick={onNavigate}>
<div className={forceMinimize ? '!hidden' : 'ml-1'}>
<Logo size={38} color="text-primary-600" />
</div>
<div className={forceMinimize ? '!block' : 'hidden tablet:max-sidebar-compact:!block'}>
<div className={forceMinimize ? '!block' : 'hidden'}>
<Icon name="flaskFrame" size={38} color="#3971ff" />
</div>
</Link>
</div>

<div
className={`mt-6 px-3 ${forceMinimize ? '!flex !justify-center !px-2' : 'tablet:max-sidebar-compact:!flex tablet:max-sidebar-compact:!justify-center tablet:max-sidebar-compact:!px-2'}`}
>
<PublishMenu forceMinimize={forceMinimize} />
<div className={`mt-6 px-3 ${forceMinimize ? '!flex !justify-center !px-2' : ''}`}>
<PublishMenu forceMinimize={forceMinimize} onItemSelected={onNavigate} />
</div>

<Navigation
currentPath={pathname || ''}
onUnimplementedFeature={handleUnimplementedFeature}
forceMinimize={forceMinimize}
/>
{/* The nav and the user's documents scroll together, so a long list
never squeezes the nav. A forced icon rail has no room for them. */}
<div className="min-h-0 flex-1 overflow-y-auto">
<Navigation
currentPath={pathname || ''}
onUnimplementedFeature={handleUnimplementedFeature}
forceMinimize={forceMinimize}
inScrollArea
onNavigate={onNavigate}
/>
<SidebarDocuments onNavigate={onNavigate} className={forceMinimize ? '!hidden' : ''} />
</div>

{/* Navigation above is flex-1, so this sits at the bottom of the column.
{/* The scroll area above is flex-1, so this sits at the bottom of the column.
Only covers 768px to the right sidebar's breakpoint: below that the
bar is docked over the mobile bottom nav, and above it the funding
power card is in the right sidebar. */}
<div className={`px-2 pb-3 ${forceMinimize ? '!block' : 'hidden tablet:max-lg:!block'}`}>
<FundingPowerRailButton />
</div>

<div className={forceMinimize ? '!hidden' : 'tablet:max-sidebar-compact:!hidden'}>
<FooterLinks />
<div className={forceMinimize ? '!hidden' : ''}>
<SidebarHelpMenu />
</div>
</div>
);
Expand Down
48 changes: 5 additions & 43 deletions app/layouts/MobileBottomNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
faBars,
} from '@fortawesome/pro-light-svg-icons';
import { faXTwitter, faDiscord, faGithub, faLinkedin } from '@fortawesome/free-brands-svg-icons';
import { Sparkles, Sprout, Star } from 'lucide-react';
import { Sprout, Star } from 'lucide-react';
import { ChangelogLink } from '@/components/changelog/ChangelogLink';
import { FundingPowerBar } from '@/components/Funding/FundingPowerBar';
import { Icon } from '@/components/ui/icons';
Expand All @@ -24,9 +24,6 @@ import { useAuthenticatedAction } from '@/contexts/AuthModalContext';
import { useCurrencyPreference } from '@/contexts/CurrencyPreferenceContext';
import { useScrollContainer } from '@/contexts/ScrollContainerContext';
import { isHomeTabPath } from '@/hooks/useFundTabs';
import { useUser } from '@/contexts/UserContext';
import { useOptionalAIMode } from '@/components/AIMode/AIModeContext';
import { isHubEditorOrModerator } from '@/utils/permissions';

interface NavItem {
label: string;
Expand All @@ -35,15 +32,12 @@ interface NavItem {
isMore?: boolean;
requiresAuth?: boolean;
isHome?: boolean;
/** Toggles the AI Mode overlay in place instead of navigating. */
isAIMode?: boolean;
}

// Additional navigation items not in the bottom bar
const moreNavItems: NavItem[] = [
{ label: 'Endowment', href: '/endowment', iconKey: 'endowment' },
{ label: 'Journal', href: '/journal', iconKey: 'journal' },
{ label: 'Notebook', href: '/notebook', iconKey: 'notebook', requiresAuth: true },
{ label: 'Lists', href: '/lists', iconKey: 'lists', requiresAuth: true },
];

Expand All @@ -55,9 +49,6 @@ const isPathActive = (path: string, currentPath: string, isHome?: boolean): bool
if (path === '/my-funding') {
return currentPath === '/my-funding';
}
if (path === '/notebook') {
return currentPath.startsWith('/notebook');
}
if (path === '/journal') {
return currentPath.startsWith('/journal');
}
Expand All @@ -79,17 +70,11 @@ export const MobileBottomNav: React.FC = () => {
const { executeAuthenticatedAction } = useAuthenticatedAction();
const { showUSD, toggleCurrency } = useCurrencyPreference();
const scrollContainerRef = useScrollContainer();
const { user } = useUser();
const aiMode = useOptionalAIMode();

// Moderators and hub editors, the only users the assistant admits, get it
// in the bar where Peer Review sits for everyone else.
const mainNavItems: NavItem[] = [
{ label: 'Home', href: '/', iconKey: 'home', isHome: true },
{ label: 'My Funding', href: '/my-funding', iconKey: 'fund', requiresAuth: true },
isHubEditorOrModerator(user)
? { label: 'Assistant', iconKey: 'assistant', isAIMode: true }
: { label: 'Peer Review', href: '/peer-review', iconKey: 'peer-review' },
{ label: 'Peer Review', href: '/peer-review', iconKey: 'peer-review' },
{ label: 'Wallet', href: '/researchcoin', iconKey: 'wallet' },
{ label: 'More', isMore: true, iconKey: 'more' },
];
Expand Down Expand Up @@ -120,10 +105,6 @@ export const MobileBottomNav: React.FC = () => {
setIsMoreOpen(true);
return;
}
if (item.isAIMode) {
aiMode?.toggle();
return;
}

if (item.requiresAuth) {
executeAuthenticatedAction(() => router.push(item.href!));
Expand Down Expand Up @@ -154,15 +135,6 @@ export const MobileBottomNav: React.FC = () => {
color={iconColor}
/>
);
case 'assistant':
return (
<Sparkles
size={iconSize}
color={iconColor}
strokeWidth={isActive ? 2.25 : 2}
fill={isActive ? iconColor : 'none'}
/>
);
case 'peer-review':
return (
<Star
Expand Down Expand Up @@ -201,14 +173,6 @@ export const MobileBottomNav: React.FC = () => {
color={iconColor}
/>
);
case 'notebook':
return (
<Icon
name={isActive ? 'notebookBold' : ('labNotebook2' as IconName)}
size={iconSize}
color={iconColor}
/>
);
case 'lists':
return (
<FontAwesomeIcon
Expand Down Expand Up @@ -264,11 +228,9 @@ export const MobileBottomNav: React.FC = () => {
{mainNavItems.map((item) => {
const isActive = item.isMore
? isMoreActive || isMoreOpen
: item.isAIMode
? Boolean(aiMode?.isOpen)
: item.href
? isPathActive(item.href, pathname, item.isHome)
: false;
: item.href
? isPathActive(item.href, pathname, item.isHome)
: false;

return (
<button
Expand Down
Loading
Loading