LibreChat/client
Danny Avila 0e160d2ba0
📱 style: Consolidate the Mobile Chat Header (#14843)
* ♻️ refactor: Extract `useNewChat` as the Single New-Chat Path

The new-chat sequence (clear the outgoing conversation's cached messages,
invalidate the messages query, reset the conversation atom) existed in
three places: the sidebar's `NewChatButton`, the `newChat` keyboard
shortcut, and an unrendered `Nav/NewChat` component.

Consolidate into `hooks/Chat/useNewChat`. The panel switch stays an
optional `onNewChat` callback rather than living in the hook, because
`useActivePanel` throws outside `ActivePanelProvider` and the chat header
sits outside it — the upcoming header button needs this seam.

`useKeyboardShortcuts` consumes the returned `newConversation` so the file
still instantiates `useNewConvo` exactly once.

Delete `Nav/NewChat`: it was reachable only through its own barrel export,
and carried a stale `max-md:hidden` plus a `data-testid` that collided
with the sidebar's button.

`handleNewChatClick` now also defers on shift-click, so shift-click opens a
new window like any other link.

`ExpandedPanel.spec` mocks the new hook — it reaches `useNewConvo` by deep
path, which escapes the spec's `~/hooks` barrel mock.

* ♻️ refactor: Split Header Action Logic Out of Its Buttons

Lift the behaviour behind the compare and temporary-chat header buttons
into `useMultiConvo` and `useTemporaryChat`, leaving each component as a
thin trigger. The upcoming mobile overflow menu needs the same actions as
menu items, and the visibility rules (assistants have their own comparison
surface; temporary chat can't be toggled mid-thread) have to stay in one
place rather than being restated per surface.

Add the header's new-chat button, consuming `useNewChat`. It renders as an
anchor to `/c/new` so modified clicks still open a tab, and uses a distinct
`data-testid` from the sidebar's button so queries can't match both.

`useTemporaryChat` toggles through a functional updater, dropping the
`useRecoilCallback` that existed only to close over the current value.

No visual change yet — the header layout lands next.

* 📱 style: Fold the Mobile Header Into Four Targets

The mobile header was a horizontally scrolling strip of up to seven
controls, each in its own outlined box, so nothing grouped and nothing
receded. The overflow was hidden rather than solved: ModelSelector alone is
capped at 70vw (273px) and the side clusters need ~130px, which does not
fit a 390px phone.

Mobile now reads: sidebar toggle, model selector, new chat, ellipsis.

Lift the bookmark and export/share menu items into `useBookmarkItems` and
`useExportShare`, each returning the items plus the dialog instance the
surface must render. Both menus already built `MenuItemProps[]` internally,
so the desktop buttons keep their exact markup and simply consume the hook
— the two surfaces cannot drift apart.

`HeaderMenu` composes those with the compare and temporary-chat actions.
Bookmarks nests through `subItems` rather than flattening every tag to the
top level, permission gates decide membership, and the trigger does not
render when nothing survives — reachable, since export/share self-hides on
a new conversation.

Layout is one DOM order serving both breakpoints; hidden items generate no
flex gap, so each collapses without reordering. Branching is CSS-only:
`useMediaQuery` resolves after paint, and the old
`isSmallScreen ? <OpenSidebar/> : null` popped the row a frame late on
every mount.

`overflow-x-auto` is gone. It hid the overflow instead of fixing it, and it
is a horizontal-swipe sink the later edge-swipe work needs removed.

Presets stays a visible mobile icon for now: `PresetItems` uses Radix's
`Close`, which throws outside a Popover root, so folding it needs a
controlled + anchored menu and browser verification.

Also drops two stray `console.log` calls carried along from the bookmark
mutation handlers.

* 🩹 fix: Address Codex Findings on the Mobile Header Menu

`separate: true` marks an entry as *being* a divider — `DropdownPopup`
returns only a `MenuSeparator` for it and drops the item. Setting the flag
on Share and on temporary chat therefore deleted those actions whenever an
earlier group existed. Push standalone divider entries instead.

The spec missed this because its `DropdownPopup` mock rendered every label
regardless of the flag. It now mirrors the real contract — dividers replace
items, `show: false` entries are dropped — and asserts both actions survive
alongside their dividers.

Gate the bookmark tags query on the bookmark permission. `HeaderMenu`
mounts unconditionally and called `useBookmarkItems` before the permission
result applied, so users without `BOOKMARKS:USE` issued a forbidden request
on every chat header mount; the old header dodged this by mounting
`BookmarkMenu` only after the check.

Restore two states the collapsed trigger had dropped: the shared-link
indicator and its active-link label, and a visible checked state for
temporary chat, which previously only reached assistive tech through
`aria-checked` while the old button switched to `bg-surface-active`.

Compose both new controls from the shared `Button` primitive with the same
override `OpenSidebar` already uses, rather than restating the bordered
icon-button recipe locally.

* 🐛 fix: Give the Overflow Menu's Share Indicator Its Own Test Id

Restoring the shared-link indicator on the mobile trigger reused the id
`ExportAndShareMenu` already owns. Both headers stay mounted and are only
hidden by CSS, so `getByTestId('header-shared-link-indicator')` matched two
elements and `shared-links.spec.ts` failed on a strict-mode violation.

Distinct id, matching the new-chat button, which was already separated from
the sidebar's for the same reason.
2026-08-14 18:14:05 -04:00
..
public 🪢 feat: Langfuse Fanout Connection Setting (#14108) 2026-07-29 18:33:10 -04:00
scripts
src 📱 style: Consolidate the Mobile Chat Header (#14843) 2026-08-14 18:14:05 -04:00
sw 🛟 fix: Auto-Recover from Stale Service Worker Assets After Deploys (#13686) 2026-06-11 11:57:06 -04:00
test 🔒 chore: Upgrade react-router-dom to v7.18.2 (security) (#14582) 2026-08-01 17:19:52 -04:00
babel.config.cjs
check_updates.sh
index.html 📡 feat: add rum browser page-load diagnostics (#14106) 2026-07-05 11:32:53 -04:00
jest.config.cjs 🚀 chore: Prepare v0.8.8-rc1 (#14394) 2026-08-14 03:24:59 -04:00
jest.resolver.cjs ⬆️ chore: Migrate off deprecated @ariakit/react-core to @ariakit/react-components (#13940) 2026-06-24 23:13:57 -04:00
nginx.conf 🐳 feat: Bundle Admin Panel in Docker Compose Stacks (#13876) 2026-06-22 16:59:08 -04:00
package.json 🚀 chore: Prepare v0.8.8-rc1 (#14394) 2026-08-14 03:24:59 -04:00
postcss.config.cjs
tailwind.config.cjs 🎨 feat: Add Versioned Theme Foundation (#14709) 2026-08-10 13:41:03 -04:00
tsconfig.json 👷 ci: Type-check the Client Workspace (#13560) 2026-06-06 18:40:31 -04:00
vite.config.ts 🎨 refactor: adopt the @librechat/client design system (semantic color tokens + component migration) (#13879) 2026-08-06 09:15:17 -04:00