[pull] main from danny-avila:main - #259
Merged
Merged
Conversation
* fix: inspect stored file locators per message * fix: declare traversal diagnostics for isolated declaration builds * refactor: inject locator traversal reporting from application callers * fix: propagate traversal diagnostics across protected entry points * fix: complete middleware reporters and hydration message counts
* 🕵️ style: Mark Temporary Chat with the Incognito Icon The dashed speech bubble (`MessageCircleDashed`) read as a generic chat glyph rather than a retention cue, and its dashes break up at 16px. Every temporary chat surface now uses lucide's `hat-glasses`, the hat-and-glasses incognito mark: the header toggle, the read-only indicator chip once a conversation has started, the overflow menu row, and the landing empty state. `hat-glasses` landed in lucide 0.528.0, so `lucide` and `lucide-react` move from `^0.525.0` to `^0.528.0` in `client` and `packages/client` (dependencies and peers). That is the smallest bump that carries the icon; lucide redraws existing icons between minors and the whole UI draws from it, so jumping to the current 0.577 would bring unrelated visual churn. `useChatBadges` imported the old icon without using it; the import is dropped. * 🧹 style: Keep the Model Disclaimer on the Landing Page Only The footer disclaimer rendered under the composer in every conversation, where it repeats itself on each turn and costs a line of the thread. It now renders only on the landing page, where it is first seen. `Footer` is untouched, as are its other hosts: the auth screens and the public shared-link view still render it. The component is a zero-height `relative` wrapper around an `absolute bottom-0` bar, so dropping it from the conversation branch shifts no layout. * 📱 fix: Run the Mobile Composer to the Screen Edges Below `sm` the composer is full-bleed — no horizontal padding, squared off at the bottom, flush with the viewport floor — but it was inset on two sides there: - `.scrollbar-gutter-spacer` reserved the message column's scrollbar band (8px) on the composer's trailing edge. That lines the composer up with the thread on desktop, but on mobile it only cut a dead strip off its right side, so the padding now applies from `sm` up. Its two users, the composer band and the scroll-to-bottom control, move together, so the control still stacks over the send button. - The composer surface carried `pb-4` under its action row below `sm` (`sm:pb-0` above), leaving 16px of empty surface between the buttons and the bottom of the screen. Desktop metrics are unchanged: the band still reserves the gutter, the composer still clears the floor by `sm:mb-10`, and the surface still resolves to `pb-0`. * 🔅 style: Mute the Landing Page Disclaimer The footer sat in `text-primary`, the same weight as the greeting above it, so the least important line on the landing page read as one of the most prominent — hardest to ignore in the light theme, where it lands at 16.1:1 on white. It now takes `text-muted`, the quietest text token that still clears AA for 12px copy on `bg-presentation`: 5.11:1 on white and 7.93:1 on the dark canvas. The contrast modes collapse every text token to pure black or white, so `high-contrast-light` and `high-contrast-dark` keep the disclaimer at 21:1. The links move with it instead of keeping the brighter `text-secondary`: the underline carries the affordance, and a link that outshines its own sentence puts the emphasis back where this change takes it from. The text stays at the bottom of the page. The landing hierarchy is the composer and its title first, the sidebar second, and moving the disclaimer under the text field would place it above both. * 💠 style: Draw the Empty Prompts Panel Like Its Neighbours The prompts side panel hand-rolled its own empty card — the same circular icon, title and caption as the bookmarks, memories and schedules panels, but written out again and a shade off: `border-border-medium` where the shared card uses `border-border-light`. It now renders the `EmptyState` primitive the other panels use, which is where that appearance is owned. The strings, the icon and the list's own `my-2` are unchanged, so the panel reads the same apart from the border it was never meant to differ on. * 📐 fix: Close the Gap Under the Composer in a Conversation From `sm` up the composer band left 40px under itself, the height of the disclaimer that used to sit there. A started conversation no longer carries one, so the composer floated a line above the viewport floor with nothing in the gap. The clearance now follows what is underneath. `ChatView` owns that fact — it is the same `isLandingPage` it gates the footer on — and passes it to `ChatForm`: - landing, centred composer: `sm:mb-28`, unchanged - landing, composer at the bottom: `sm:mb-10`, the clearance the disclaimer needs, unchanged - started conversation: `sm:mb-4`, enough to show the surface's own shadow Below `sm` the composer still runs to the viewport floor in every state. `ChatForm` cannot answer this for itself: deriving it from `conversation?.messages?.length === 0` reads false while `messages` is undefined, which silently drops the un-centred landing page to the conversation clearance and overlaps the disclaimer. `SubagentThreadPanel` states that its own bottom padding matches the main composer's so the two surfaces end on one line when the panel is open beside a thread, so it moves with it. * 🎞️ fix: Slide the Composer Between Welcome Screen and Conversation With "Center Chat Input on Welcome Screen" off, the welcome composer sits at the bottom with the disclaimer's clearance under it and a conversation's sits 24px lower. React keeps the same form node across that navigation, so the band had everything it needed to travel and jumped instead: `margin-bottom`, the property that carries it, was not in the transition list — only `max-width` was, from the chat-width preference. `margin-bottom` now transitions with it, 300ms on Tailwind's default easing, in both directions and from the centred welcome composer as well. Reduced motion gets the new position outright (`motion-reduce:transition-none`): this is a slide across the page, not decoration. *↔️ fix: Hold the Composer Still Across the Welcome Screen Opening a conversation from the welcome screen slid the composer 4px to the left. A conversation's composer band reserves the band the message column holds back for its scrollbar (`.scrollbar-gutter-spacer`, 8px here), which is what lines the composer up with the messages; the welcome screen reserved nothing, so its composer was centred on the full region and half the gutter off from where it was about to land. Now that `margin-bottom` animates, the sideways step ran alongside the downward one. The gutter is reserved once per state, wherever the centring happens: the conversation keeps it on the band around the composer, and the welcome screen takes it on the column that centres greeting and composer together, so both move as one and the composer lands where the message column will put it. Measured across the navigation — 1280 and 1024 wide, centred and bottom composer, sidebar open and collapsed: the surface keeps its exact left edge and width. Below `sm` the reservation does not apply and the composer stays full-bleed on both sides of the navigation. * 📏 fix: Measure the Scrollbar Gutter Before the Welcome Screen Reserves It The welcome screen holds back the band the message column reserves for its scrollbar, so the composer lands where the message column will put it. On a fresh load no message column has mounted, so `useScrollbarGutter` has published nothing and the spacer fell back to `--scrollbar-size`: a fixed 8px, correct only where the app's own `::-webkit-scrollbar` width applies. On an overlay-scrollbar platform the real band is zero, so the welcome screen started 4px off-centre and the composer stepped sideways as soon as the first conversation measured the band for real — the jump this change set out to remove, inverted. The gutter is now seeded from a detached probe built like the column itself (`overflow-y: auto` with `scrollbar-gutter: stable`), which answers the same question for the platform in front of the user: the scrollbar width, and zero where scrollbars overlay. A mounted column measures itself and is authoritative, so the seed only fills the gap before the first one exists and never overwrites a published measurement. Reported by Codex on PR #15838: #15838 (comment) * 🧪 test: Cover the Composer and Welcome Screen Behaviours Nine browser scenarios for what this change promises, in the mock harness against the in-process fake model, tagged so each one is addressable: - `welcome-screen-disclaimer-only`, `welcome-disclaimer-readable-while-muted`: the disclaimer stays on the welcome screen, leaves the conversation, and clears AA contrast against the canvas it is painted on — the dark project measures the dark palette, the light one the light palette. - `composer-holds-position-into-conversation`, `composer-holds-position-with-overlay-scrollbars`: the composer keeps its left edge and width across the navigation and lines up with the message column, both with the app's own scrollbars and on a platform whose scrollbars reserve nothing. - `composer-slides-down-into-conversation`, `composer-settles-instantly-under-reduced-motion`: the clearance change runs as a `margin-bottom` transition on the composer's own form, and runs no transition at all under `prefers-reduced-motion`. - `mobile-composer-reaches-screen-edges`: below `sm` the surface spans the viewport and its action row is the last thing in it. - `temporary-chat-marked-with-incognito-icon`: turning the mode on marks the toggle and the welcome screen with the outline incognito mark. - `empty-prompts-panel-matches-other-panels`: the empty prompts card and the empty bookmarks card are drawn identically. Each scenario skips the projects whose viewport it does not describe, rather than asserting a desktop layout under mobile emulation. * 🩹 fix: Seed the Gutter Before Paint and Keep Configured Footers Three findings from the review round on eec4b37, one root cause each. The gutter seed measured in a passive effect, so the welcome screen's first frame still reserved the `--scrollbar-size` token and recentred by 4px once the real band was known — the load-time half of the shift this reservation removes. It measures in a layout effect now, before the browser paints. Scoping the disclaimer to the welcome screen also unmounted the only authenticated-chat renderer of `customFooter`, and of a deployment's privacy policy and terms links. `Footer` takes `configuredOnly`: the generic disclaimer is dropped, operator content is kept, and with nothing configured it renders nothing rather than an empty bar. The conversation renders it again under that scope. The mobile composer spec gated itself on viewport width, and the repository's mock config has one Desktop Chrome project, so CI skipped it and reported nothing. It declares the viewport it describes with `test.use`. Two scenarios cover the first two: - `welcome-composer-paints-in-its-final-position`: samples the composer's x from the document's first frame on a platform whose scrollbars reserve nothing, and fails on any later correction. - `configured-footer-stays-in-a-conversation`: serves a configured `customFooter` through the startup-config route and finds it on both the welcome screen and inside a conversation. Findings: #15838 (comment) #15838 (comment) #15838 (comment) * 🧹 test: Drop the First-Paint Sampler, Keep the Layout Effect The sampler recorded the composer's x from the document's first frame to catch a late recentring on a platform whose scrollbars reserve nothing. It cannot fail: with the seed measured in a passive effect it still recorded a single position in all three projects, because React flushes that effect before the browser paints the commit it belongs to. A test that passes with and without the behaviour it describes pins nothing. The measurement stays in a layout effect: a value the layout reads belongs before paint by construction, whatever a given runtime's flush order happens to be. The reservation itself is covered by `composer-holds-position-with-overlay-scrollbars`, which does fail without it — 4px, the half-band Codex reported. * 🧷 fix: Clear the Configured Footer and Remeasure the Band on Contrast Two findings from the round on d942b7c, one invariant each. Keeping a configured footer in conversations left the composer reserving 16px above an absolutely positioned bar about 32px tall, so the bar painted over the composer's action row and would have taken its clicks. The clearance follows what actually renders beneath the composer now, not which page it is: `ChatView` reads `useConfiguredFooter` once and both decisions — whether to render the footer, and how much room to leave for it — come from that same answer. `ChatForm` takes `footerBelow` and keeps the welcome screen's rhythm for the welcome screen. The band is not a constant of the platform either: the contrast modes widen the app's own scrollbar to 0.75rem, and a reader can turn contrast on — or have the OS turn it on — while the welcome screen is the only thing mounted, with no message column to republish. The seed remeasures when `ThemeProvider` flips a class on the document element, which is the thing that changed the band. Two scenarios, both failing before the fixes: - `configured-footer-clears-the-composer`: with `customFooter` configured, the bar sits at or below the composer's bottom edge and the composer still takes a click at its own bottom row. - `composer-holds-position-across-a-contrast-switch`: contrast turned on while the welcome screen is up, then a conversation opened; the composer keeps its left edge and width. Findings: #15838 (comment) #15838 (comment) * 🪡 fix: Align the Subagent Panel With the Conversation's Footer The panel's bottom band states that it matches the main composer's clearance so the two surfaces end on one line beside each other. That clearance became footer-dependent in the previous commit, and the panel kept a fixed `pb-4`, so a deployment with a configured footer ended them 24px apart. The panel reads the same answer the composer reads — `useConfiguredFooter` — and clears the bar when there is one. Three consumers now share one source for the question "does a footer render beneath this surface": `ChatView`, `ChatForm` through `footerBelow`, and this panel. The panel's own alignment has no browser scenario: reaching this surface needs an agent with subagents, a dispatched task and an open control footer, which is a fixture of its own. The shared cause is covered by `configured-footer-clears-the-composer` on the main composer. Reported by Codex on PR #15838: #15838 (comment) * 🧵 fix: Hold the Footer Clearance Through a Cold Load and a Stale Band Three findings from the round on 55106cd. Whether a conversation carries a footer is an answer from the startup config, and on a cold `/c/<id>` load it arrives after the composer is painted. `useConfiguredFooter` now reports `{ present, resolved }`, and the two readers use them differently on purpose: nothing renders until the answer arrives, because there is nothing to render, but the clearance is held from the first frame, because guessing "no footer" moves the composer twice. The measured scrollbar band outlives the chat screen on purpose, and the screens it outlives can change it — the auth layout carries a theme selector, and a contrast switch there widens the app's own scrollbar while no chat column is watching. The seed measures on every mount now instead of trusting a value it did not publish. `@librechat/client`'s peer range kept its previous line as well as the new one (`^0.525.0 || ^0.528.0`): only `/client` consumes `HatGlasses`, and the shared package still builds against either, so raising the published minimum would have rejected downstream installs for an icon it does not use. Its devDependency stays on 0.528, which is what it is tested against. Two scenarios, both failing before the fixes: - `configured-footer-clearance-survives-a-cold-load`: the startup config is served 700ms late with a custom footer, and the composer's bottom is sampled from the document's first frame; any correction fails it. - `welcome-screen-remeasures-a-stale-gutter`: a 40px reservation is left behind before the app boots, as another screen would, and the welcome screen has to replace it rather than inherit it. Findings: #15838 (comment) #15838 (comment) #15838 (comment) * 🧠 fix: Remember the Footer Answer Instead of Guessing It Reserving the footer's band while the startup config was in flight fixed the cold load of a deployment that configures a footer and broke the cold load of one that does not: the composer took `sm:mb-10`, then dropped 24px when the answer said there was nothing to clear. Both defaults are wrong for one of the two deployments, because the layout was being derived from an answer that does not exist yet. `useConfiguredFooter` remembers the answer instead. A deployment's footer configuration is a deployment-lifetime fact, so the last answer it gave is the right thing to lay out against while `/api/config` is in flight; it is recorded once the query resolves. Every load after the first is exact in either kind of deployment, and a first-ever visit falls back to LibreChat's default — no configured footer — and settles once if the deployment disagrees. `ChatView` and the subagent panel drop the `!resolved` term: `present` already carries the remembered answer, so the value is the same before and after the config answers. Scenarios, both sampling the composer's bottom from the document's first frame with the config served 700ms late: - `configured-footer-clearance-survives-a-cold-load` now primes the answer with a first visit and asserts the load after it, which is the load a returning user gets. - `default-clearance-survives-a-cold-load` covers the other branch: a deployment that configures nothing must not move either. Reported by Codex on PR #15838: #15838 (comment) * 🛟 fix: Treat a Failed Startup Config as No Answer React Query reports a request that exhausted its retries as fetched, with no data, so reading `isFetched` turned a failed `/api/config` into a confirmed "nothing configured": a deployment remembered as having a footer lost the clearance that footer needs, and the memory was overwritten with the guess, so the composer moved again on the next load that succeeded. `useConfiguredFooter` reads `isSuccess`. A failure now answers nothing: the remembered deployment state stands, and nothing is recorded until a response actually arrives. `config-failure-keeps-the-remembered-footer` covers it: a first visit records a configured footer, a second visit is served 500s until the retries run out, and the composer's clearance has to be the one it had. Reported by Codex on PR #15838: #15838 (comment) * 🧽 test: Drop the Config-Failure Scenario, Keep the Success Check The scenario recorded a configured footer, served `/api/config` 500s until the retries ran out, and then asserted the next successful load lays out once. It cannot fail: without a startup config the app renders no chat at all, so `useConfiguredFooter` is never mounted on the failing visit and nothing is recorded either way. The negative control — reading `isFetched` again — passes it in all three projects. The reported corruption therefore has no reachable path: the hook only observes a failed query while the chat is mounted, which requires a config that answered once, and React Query keeps that answer as cached data through a later failure. `isSuccess` stays. "A failure answers nothing" is what the code should say, and saying it costs nothing; the test that pretended to prove it does not stay. Reported by Codex on PR #15838: #15838 (comment) * 🧱 refactor: Own the Footer Memory in Jotai and Take the Landing Preference In Two repository-policy findings from the round on 5d535a6. The remembered footer answer was a raw `localStorage` read and write, which is the persistence path the client state rules replaced: it is feature-owned state that the chat surface both writes and reads, so it is a Jotai storage atom now (`configuredFooterAtom`, beside the feature that owns it). `getOnInit` is what makes it usable — the value has to be there on the first render, or it is the guess again — and only a successful config response writes to it. `ChatForm` also still subscribed to `centerFormOnLanding` directly, an app-level persisted preference the chat feature only consumes. `ChatView` already reads it for `Landing`, and now passes it in beside `isLandingPage` and `footerBelow`, so the composer's clearance is computed entirely from what the host handed it. Findings: #15838 (comment) #15838 (comment) * 🔍 fix: Give the Context Popover Its Own Foreground The popover is portaled to the body with a surface colour and no text colour, so anything inheriting \ there took the user-agent default black: the insights toggle rendered at 1.3:1 on the dark canvas and was invisible until hovered. The panel now names \ beside its surface, as the shared hover-card surface already does. * 🧮 fix: Show the Cached Share Beside Its Tokens The cached and cache-write rows were built without a max, and the row renders its share only when one is given, so both printed a bare token count while every row around them carried a percentage. Both now key the window, and the cached test pins the share. The insights toggle also names its resting colour here: the ghost variant defines only a hover colour, and every other glyph in the panel names a role.
* fix: keep quote selection updates outside React renders * fix: stop animating hidden code execution spinners * test: guard idle transcripts against continuous animations * test: Assert accepted paste behavior independently of placeholder resize
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )