* ♻️ refactor(conversation): unify spacer + scroll-to-user hooks
Merge `useConversationSpacer` and `useScrollToUserMessage` into a single
`useConversationScroll` hook to eliminate the races that caused occasional
"send message but viewport doesn't pin to the new user message" regressions.
Race fixes:
- Single `prevLengthRef` and a single send-detection effect, replacing two
hooks with independent length tracking that could disagree across renders.
- `virtuaRef` is passed in and dereferenced at call time instead of reading
`virtuaRef.current?.scrollToIndex` during render — removes the window
where the ref hadn't been attached yet when a send fired.
- Pin state is an explicit `{ index, seenActive }` ref with three clear
transitions (send / layout-bump / user-scroll-up) instead of several
cooperating refs + derived flags.
- Retries are layout-driven: each `spacerLayoutVersion` bump re-fires
`scrollToIndex` exactly once. The old 0/32/96ms timer fan-out is gone.
Also bumps `AT_BOTTOM_THRESHOLD` 100 → 300 so `atBottom` stays stable
while the spacer is settling.
* ♻️ refactor(conversation): extract sub-hooks from useConversationScroll
Split the unified conversation scroll hook into four cooperating sub-hooks
in the same file so each layer has one clear concern:
- useSpacerLayoutSignal — ResizeObserver on the spacer node → version bumps
- useSpacerHeight — natural height / mount lifecycle / shrink state
- usePinController — pin state machine + virtua-aware scroll dispatch
- useScrollShrink — scrollOffset delta → cancel pin / shrink spacer
The main hook now owns just the send-detection effect, the pin re-fire on
layout settle, and derived output. Behavior is unchanged — same 15 tests
pass — but each piece is now readable in isolation.
* ⚡️ perf(conversation): narrow VirtualizedList subscription to a boolean
VirtualizedList only needs to know whether the second-to-last message is
the user's — the full displayMessages array was never used. Move the
derivation into `dataSelectors.isSecondLastMessageFromUser` so the
component re-renders on role transitions, not on every assistant token.
* ✅ test(e2e): cover conversation scroll behavior across the auto-scroll setting
Adds three scenarios under `@AGENT-SCROLL-*` that exercise the merged
`useConversationScroll` hook end-to-end through the real chat UI:
- AGENT-SCROLL-001 — with auto-scroll ON, the viewport ends up near the
bottom once a long response has finished streaming.
- AGENT-SCROLL-002 — with auto-scroll OFF, the user's message stays
pinned to the top and the viewport does not chase the assistant.
- AGENT-SCROLL-003 — with auto-scroll ON, scrolling up mid-stream cancels
the pin and the viewport is not yanked back to the bottom afterwards.
Also extends the LLM mock with `setConfig` / `resetConfig` so scenario 3
can slow the response down enough for the mid-stream manual scroll, and
adds `presetResponses.longScrollArticle` (long enough to overflow the
viewport so scroll assertions are meaningful).
* ✅ test(e2e): cover send-time pin-to-top as its own scenario
AGENT-SCROLL-004 exercises the core pin behavior of `useConversationScroll`
independent of the auto-scroll setting: after sending a message, the user's
turn must be anchored to the top of the scrollport. Uses the slow-response
mock so the assertion runs while the spacer is still mounted.
* ✅ test(e2e): tune scroll scenarios after runtime validation
Run outcomes against a cold Next dev server (paradedb + next dev -p 3006):
- AGENT-SCROLL-001 (enabled → viewport stays near bottom) — passing
- AGENT-SCROLL-002 (disabled → user msg pinned to top) — passing
- AGENT-SCROLL-004 (send pins user msg to top) — passing
- AGENT-SCROLL-003 (mid-stream scroll-up cancels pin) — skipped
Scenario 3 is marked `@skip` until the LLM mock supports truly chunked
SSE streaming. The current mock fulfils the whole body at once, which
collapses the "mid-stream" window to a handful of ms and makes the
manual-scroll timing race-prone. The cancel-pin path is already
covered at the unit level in `useConversationScroll.test.ts`, so the
e2e placeholder just keeps the scenario on the radar.
Other tweaks for dev-mode reliability:
- Bumped setting-toggle step timeout to 90 s (turbopack cold compile of
`/settings/chat-appearance` can exceed the default 30 s on first hit)
- Relaxed the inner `networkidle` / `toBeVisible` waits there to match
- Added a matching negative-path Then ("not pinned") that would power
the skipped scenario once the mock is upgraded
* 🐛 fix(conversation): rebind pin tracking on every new turn
The message index refs that drive `latestAssistantSignature` and the
messages `ResizeObserver` were plain `useRef`s updated inside the send-
detection effect. On the render triggered by spacer state updates right
after a send, `[dataSource, displayMessages]` could be unchanged, so the
signature memo returned its cached value and the observer effect never
rebound to the new turn's user/assistant DOM nodes. Under certain commit
orderings this left spacer height tracking the previous turn and let
the pin-to-user anchor drift.
Turn the indices into state, include `assistantMessageIndex` in the
signature memo's deps, and forward the state (not a ref) to
`useSpacerHeight`. The observer now reliably rebinds to the fresh
nodes on the very next render.
Adds a unit regression covering the observer-rebind path and an e2e
scenario (`AGENT-SCROLL-005`) that sends two consecutive turns and
checks that the second user message still pins to the top.
* ♻️ refactor: use COPYRIGHT constant from branding config
* ♻️ refactor: optimize default model configuration
- Switch default provider from OpenAI to Anthropic
- Add DEFAULT_MINI_MODEL and DEFAULT_MINI_PROVIDER for lightweight tasks
- Use mini model for system agent tasks: generationTopic, topic, translation, queryRewrite
- Use mini model for memory extraction agents
- Reorder provider list: Anthropic/Google first, local solutions last
* ♻️ refactor: split lobehub models into organized folder structure
- Split large lobehub.ts (1316 lines) into lobehub/ folder
- Organize chat models by provider (openai, anthropic, google, etc.)
- Separate image models and utils into dedicated files
* ♻️ refactor: use SOCIAL_URL constant and fix button alignment in auth-error page
* ✨ feat: add MiniMax M2.1 and M2.1 Lightning models
* ♻️ refactor: remove 'enabled' property from image model configurations in lobehub
* ♻️ refactor: add COPYRIGHT_FULL constant and fix Discord icon visibility
* ✅ test: update snapshots for default provider changes
* ✅ test: fix snapshot provider values for CI environment
* 🐛 fix(e2e): intercept all LLM providers in mock instead of only OpenAI
The default provider was changed from openai to anthropic, but the LLM mock
only intercepted /webapi/chat/openai requests. Now it intercepts all providers.
* ✅ test(e2e): add Agent conversation E2E test with LLM mock
- Add LLM mock framework to intercept /webapi/chat/openai requests
- Create Agent conversation journey test (AGENT-CHAT-001)
- Add data-testid="chat-input" to Desktop ChatInput for E2E testing
- Mock returns SSE streaming responses matching LobeChat's actual format
Test scenario: Enter Lobe AI → Send "hello" → Verify AI response
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* 📝 docs(e2e): add experience-driven E2E testing strategy
Add comprehensive testing strategy from LOBE-2417:
- Core philosophy: user experience baseline for refactoring safety
- Product architecture coverage with priority levels
- Tag system (@journey, @P0/@P1/@P2, module tags)
- Execution strategies for CI, Nightly, and Release
- Updated directory structure with full journey coverage plan
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
📝 docs(e2e): add E2E testing guide for Claude
Document key learnings from implementing Agent conversation test:
- LLM Mock SSE format and usage
- Desktop/Mobile dual component handling with boundingBox
- contenteditable input handling
- Debugging tips and common issues
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* 📝 docs(e2e): add experience-driven E2E testing strategy
Add comprehensive testing strategy from LOBE-2417:
- Core philosophy: user experience baseline for refactoring safety
- Product architecture coverage with priority levels
- Tag system (@journey, @P0/@P1/@P2, module tags)
- Execution strategies for CI, Nightly, and Release
- Updated directory structure with full journey coverage plan
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
📝 docs(e2e): add E2E testing guide for Claude
Document key learnings from implementing Agent conversation test:
- LLM Mock SSE format and usage
- Desktop/Mobile dual component handling with boundingBox
- contenteditable input handling
- Debugging tips and common issues
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* update sop
* update sop
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* ✅ test(e2e): add Agent conversation E2E test with LLM mock
- Add LLM mock framework to intercept /webapi/chat/openai requests
- Create Agent conversation journey test (AGENT-CHAT-001)
- Add data-testid="chat-input" to Desktop ChatInput for E2E testing
- Mock returns SSE streaming responses matching LobeChat's actual format
Test scenario: Enter Lobe AI → Send "hello" → Verify AI response
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* 📝 docs(e2e): add E2E testing guide for Claude
Document key learnings from implementing Agent conversation test:
- LLM Mock SSE format and usage
- Desktop/Mobile dual component handling with boundingBox
- contenteditable input handling
- Debugging tips and common issues
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* 📝 docs(e2e): add experience-driven E2E testing strategy
Add comprehensive testing strategy from LOBE-2417:
- Core philosophy: user experience baseline for refactoring safety
- Product architecture coverage with priority levels
- Tag system (@journey, @P0/@P1/@P2, module tags)
- Execution strategies for CI, Nightly, and Release
- Updated directory structure with full journey coverage plan
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* add conversation case
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* add e2e tests
* fix workflow
* update workflow
* 🐛 fix(e2e): fix smoke tests i18n and timeout issues
- Unify default port to 3006 across hooks.ts and world.ts
- Reduce step timeout from 30s to 10s for faster feedback
- Fix i18n matching for featured sections (support zh-CN/en-US)
- Add mock framework foundation for future API mocking
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* 🐛 fix(e2e): save failure screenshots to file for CI artifacts
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* 🐛 fix(e2e): move PORT to global env for consistent access
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* 🐛 fix(e2e): set onboarding as completed for test user
Skip onboarding flow by setting finishedAt in test user seed