🐛 fix(conversation): pin user message to viewport top & fold long user messages#14056
Conversation
…ttles Observing the spacer DOM via ResizeObserver lets us re-fire scrollToIndex once virtua finishes measuring it and scrollSize actually expands, so the sent user message lands flush against the viewport top instead of trailing below by the spacer growth delta. Also drop the height transition on mount/grow so scrollSize jumps in a single frame; only the collapse-to-zero (unmount) still animates.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Swap execFile for a detached spawn with stdio ignored and unref, so the opened browser process no longer keeps the Vite dev process alive. Falls back to treating a 200ms "no error" window as success, and routes diagnostics through the Vite logger instead of swallowing them.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 420a3e2331
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## canary #14056 +/- ##
=========================================
Coverage 67.00% 67.01%
=========================================
Files 2105 2106 +1
Lines 179881 179986 +105
Branches 17840 21323 +3483
=========================================
+ Hits 120537 120611 +74
- Misses 59221 59252 +31
Partials 123 123
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
…sible When a very long user message is pinned to the viewport top after send, it can eat the entire viewport and leave no room for the AI reply. Wrap the user text body in a CollapsibleContent that clamps content past min(280px, 35vh) with a gradient mask and a Show more / Show less toggle. Attachments, images and page selections stay fully visible.
… callback ConversationProvider supports multiple conversation lists mounted at the same time, so a document-wide querySelector would attach to whichever spacer the DOM hands out first — possibly another panel's — and drive spacerLayoutVersion from unrelated layout ticks. Switch to a ref callback returned from useConversationSpacer and bound to the spacer div rendered by the same VirtualizedList, guaranteeing the observer tracks this instance's own spacer.
Clearing pendingScrollIndexRef alone wasn't enough — the retry wave fires at 0/32/96ms, so if the user scrolled up between send and 96ms the already-queued timers would still call scrollToIndex and yank the viewport back down, contradicting the "don't fight user intent" rule. Also invoke clearPendingPins in the same effect so the in-flight retry window is cancelled along with the pending index.
# 🚀 LobeHub v2.1.53 (20260427) **Release Date:** April 27, 2026 **Since v2.1.52:** 194 merged PRs · 17 contributors > Introduce Heterogeneous Agent — Claude Code and Codex run as first-class desktop runtimes, paired with a new Agent Signal package, sharper desktop UX, and a wave of flagship model additions. --- ## ✨ Highlights - **Introduce Heterogeneous Agent** — Claude Code and Codex run as first-class desktop agents: subagent rendering, partial-message streaming, multi-turn resume, terminal error surfacing, rich tool inspectors, and runtime polish. (#14162, #13754, #14067, #14001, #13970, #13942) - **Screen capture & Quick Chat tray** — New desktop screen capture overlay (macOS permission-gated) with Quick Chat tray and upload pipeline improvements; chat input auto-focuses on overlay mount. (#13818, #14097, #14105) - **Desktop topic & tab UX** — Dedicated topic popup window with cross-window sync, Cmd+W/Cmd+T tab shortcuts, TabBar polish, recent working directories expanded to 20, and human approval notifications. (#13957, #13983, #13972, #14036, #14092) - **Git workflow built-in** — One-click pull/push from the branch chip, ahead/behind badge, and submodule/worktree repo detection. (#14041, #13980, #13978) - **Agent Signal package** — New `@lobechat/agent-signal` runtime for dynamic memory feedback signals, with OTel metrics and self-iteration in Lab. (#14157, #14170, #14159, #14169, #14187) - **New models** — Claude Opus 4.7 with `xhigh` effort tier, GPT-5.5, DeepSeek V4 Flash/Pro with reasoning slider, Kimi K2.6, MiMo-V2.5/Pro, gpt-image-2, Qwen3.6 Flash/Plus, and Pixverse-c1. (#13903, #14147, #14114, #14004, #14089, #14039, #13923) - **New providers** — OpenCode Zen, OpenCode Go, and Azure OpenAI Router runtime. (#13943, #14064, #13823) - **Mobile settings overhaul** — Full settings menu and responsive profile layout for mobile. (#14019) --- ## 🏗️ Heterogeneous Agent - Claude Code runtime, working-directory awareness, and sidebar polish. (#13970) - CC subagent rendering with persistent streamed text; parallel-tool orphan fix. (#14001, #13968, #14024) - Per-step usage persisted to each step assistant message. (#13964) - Per-phase workflow expand defaults; full-expand toggle with three-level expansion. (#14171, #13906) - Hetero-mode actions bar; tool inspector polish. (#13963, #14034, #14030) - Codex desktop integration with rich tool rendering and devtools preview. (#14067, #14100) - Codex terminal error surfacing and CLI output tracing. (#14166) - Tighten `isCanUseVision` default and add aggregator fallback. (#14172) - Persist `ccSessionId` in topic metadata for CC multi-turn resume. (#13902) - CC account card, topic filter, and integration polish. (#13955, #13942, #13950) - Token-level deltas streamed via `--include-partial-messages`. (#13929) --- ## 🧠 Agent Signal & Self-Iteration - New `@lobechat/agent-signal` package with dynamic feedback signals. (#14157) - AgentSignalRuntime wired through agent-tracing and observability-otel metrics. (#14170, #14159) - Self-iteration feature flag added to Lab; front-side flag check. (#14169, #14186) - Signal policy for receiving memory feedback dynamically. (#14187) --- ## 💬 Conversation - Queue follow-up sends during running CC turns. (#14179) - Persist per-topic chat scroll position; pin user message + fold long messages. (#14191, #14056) - Inline resend when editing last user message. (#14080) - Disable first-block markdown streaming to prevent flicker. (#14193, #13904) - Prevent Markdown stream replay when vlist remounts streaming items. (#14086) - Stop repinning after manual scroll; unify scroll-to-user + spacer hooks. (#14099, #14132) --- ## 📱 Platforms & Integrations ### Desktop / Electron - Screen capture overlay, Quick Chat tray, and upload pipeline improvements. (#13818) - macOS permission gate for screen capture; auto-focus chat panel input. (#14097, #14105) - Dedicated topic popup window with cross-window sync. (#13957) - TabBar polish: `+` button for new topic, dark theme blend, close icon by default. (#13972, #14203, #13973) - Recent working directories expanded from 5 to 20; submodule/worktree repo detection. (#14036, #13978) - Cmd+W / Cmd+T tab shortcuts and global shortcut consolidation. (#13983, #13880) - Linux icon configuration; human approval desktop notifications. (#14042, #14092) ### Git Workflow - One-click pull/push from branch chip; ahead/behind badge with refactored GitCtr. (#14041, #13980) ### Mobile - Full settings menu and responsive profile layout. (#14019) - Agent route added to mobile router; mobile agent topic route registered. (#14103, #14158) - Session list skeleton row layout corrected. (#14040) ### Bot / Messaging - DM strategy support; bot emoji and markdown render optimization. (#14201, #14091, #14140) - Slack webhook fix; bot platform setup guide reference. (#14052, #14121) --- ## 🤖 Models & Providers ### New models - **Claude Opus 4.7** with `xhigh` effort tier; strip temperature/top_p. (#13903, #13909) - **GPT-5.5**. (#14147) - **DeepSeek V4** Flash/Pro cards with reasoning slider; cache-hit and Pro discount pricing. (#14114, #14209, #14196, #14131) - **Kimi K2.6** model with LobeHub-hosted card. (#14004, #14006) - **MiMo-V2.5 / V2.5-Pro**. (#14089) - **gpt-image-2**, **Qwen3.6 Flash/Plus**, **Pixverse-c1**. (#14039, #13923) ### New providers - **OpenCode Zen** and **OpenCode Go** with env-var support. (#13943, #14064) - **Azure OpenAI Router** runtime support. (#13823) - Model alias mapping for image and video runtimes. (#13896) - Seedance video models migrated to Dreamina. (#14144) ### Runtime reliability - Sanitize invalid tool_call arguments to unbreak strict providers. (#14033) - Tolerate null `function.name` in streaming tool_call deltas. (#14139) - Preserve Gemini 3 `thoughtSignature` in `call_tools_batch` normalization. (#14032) - Downgrade `image_url` parts when target model lacks vision. (#14029) - Preserve Cloudflare provider error context. (#14136) - Use `safety_identifier` for OpenAI Responses API. (#14148) - Unwrap underlying PG error in `formatErrorEventData`. (#14038) --- ## 🖥️ User Experience - **Onboarding** — Preset agent naming suggestions, structured hunk ops for `updateDocument`, persona analytics snapshot, footer promotion pipeline, wrap-up button. (#13931, #13989, #13930, #13853, #13934) - **Document workflow** — Agent documents promoted as primary workspace panel; history management and compare workflow; web-crawl docs associated with agent documents. (#13924, #13725, #13893) - **cmdk** — Agent identity surfaced on topic search results; topic/message search scoped to current agent. (#14204, #13960) - **Floating chat panel** and workspace improvements. (#13887) - **Topic completion status** with dropdown action and filter. (#14005) --- ## 🔧 Tooling - Redis-backed feature flag provider for runtime config. (#14098) - Vite upgraded to 8.0.0 with Rolldown strict execution order. (#12720, #14058) - `@lobechat/model-bank` automated npm release with provenance. (#14015, #14017, #14018) - Skill activation fallback when `activateTools` cannot find identifier. (#14010) - Cron tool: timezone and existing jobs injected into system prompt; clarified `lobe-gtd` and `lobe-cron` descriptions. (#14012, #14013) --- ## 🔒 Security & Reliability - **Security:** uuid bumped to v14 (advisory). (#14083) - **Security:** validate avatar URL and scope old-avatar deletion to owner. (#13982) - **Security:** clear OIDC sessions on better-auth signout; return 401 (not 500) for expired OIDC JWT. (#13916, #14014) - **Reliability:** scope pending-approval check to current assistant turn. (#14182) - **Reliability:** sanitize heterogeneous-agent attachment cache filenames. (#13937) - **Reliability:** reduce subagent task status error noise. (#14026) --- ## 👥 Contributors Huge thanks to **17 contributors** who shipped **194 merged PRs** this week. @hardy · @shaun0927 · @hezhijie0327 · @sxjeru · @arvinxx · @Innei · @tjx666 · @lijian · @neko · @rdmclin2 · @AmAzing129 · @sudongyuer · @CanisMinor · @rivertwilight Plus @lobehubbot and renovate[bot] for maintenance. --- **Full Changelog**: v2.1.52...v2.1.53
💻 Change Type
🔗 Related Issue
N/A
🔀 Description of Change
1. Pin sent user message to viewport top after the conversation spacer settles
After sending a message, the virtualized list tries to scroll the new user message to the viewport top via
scrollToIndex(userIndex, { align: 'start' }). In practice it consistently fell short — the message landed a few pixels below the top — because of two timing issues between the scroll and the conversation spacer that fills the remaining viewport:heighttransition delayedscrollSize. The spacer div carriedtransition: height 200ms easefor both grow and shrink. On mount, virtua couldn't extendscrollSizeby the full spacer height within the 0/32/96ms retry window, soscrollToIndexclamped to the then-current max offset.useScrollToUserMessageonly re-fired on the booleanspacerActiveturning true, which happens before virtua actually measures the spacer DOM and extendsscrollSize.Fixes:
VirtualizedList.tsx— the spacer only animates on collapse-to-zero (unmount). Any non-zero height change (initial mount, shrink as assistant grows) is applied instantly soscrollSizeupdates in a single frame. The spacer div getsdata-conversation-spacer="true"for observation.useConversationSpacer.ts— adds aResizeObserveron the spacer DOM once mounted, bumping aspacerLayoutVersioncounter whenever its real size changes.useScrollToUserMessage.ts— consumesspacerLayoutVersionandscrollShrinking. Re-firesscrollToIndexeach time the spacer's real layout settles (not just when it mounts), and clears the pending pin when the spacer unmounts or the user scrolls up manually (so we don't fight user intent).2. Fold long user messages so AI response stays visible
Follow-up concern once auto-scroll actually works: a very long user message pinned to the viewport top would eat the entire viewport and leave no room for the AI response.
CollapsibleContentcomponent wraps the user message's text body (Markdown / RichText). When natural height exceedsmin(280px, 35vh)by more than 32px, the body is clamped with a gradient mask and aShow more/Show lesstoggle. Attachments (images, files, videos, page selections) remain fully visible.chat:messageLongCollapse.expand/chat:messageLongCollapse.collapseinen-USandzh-CNfor dev preview.3. Don't block Vite dev server when opening the Debug Proxy
openExternalBrowserinvite.config.tsusedexecFile, which keeps the parent (Vite) tied to the opened browser. Swapped to a detachedspawnwithstdio: 'ignore'+unref(), wired warnings through the Vite logger, and treat a 200ms "no spawn error" window as success. No more hanging dev server on exit.🧪 How to Test
Tested locally
Added/updated tests
No tests needed
Open a conversation, send a short message in a long thread → the new user message lands flush against the viewport top.
Send a very long user message (e.g. paste several paragraphs) → the message is folded with a
Show morebutton; toggling reveals/hides the full text.During AI streaming, scroll up manually → the view stays put; no re-pinning.
Repeat on short threads where content doesn't fill the viewport — spacer mounts and pin still works.
bun run dev:spa, open the printed Debug Proxy URL, confirm the browser opens and the Vite process exits cleanly on Ctrl+C.Unit tests:
bunx vitest run --silent='passed-only' 'src/features/Conversation/ChatList/hooks/useScrollToUserMessage.test.ts' 'src/features/Conversation/ChatList/hooks/useConversationSpacer.test.ts' 'src/features/Conversation/Messages/User/components/MessageContent.test.tsx'.📸 Screenshots / Videos
N/A — positional / interactive change, best observed live.
📝 Additional Information
No API or data-shape changes. Behavior changes are limited to (a) auto-scroll after sending a new message, (b) the visual clamp of long user messages, and (c) the dev-only Debug Proxy auto-opener in Vite.