feat(chat): migrate history list to @tanstack/react-virtual with deterministic mobile history loading

- Replace virtua with @tanstack/react-virtual for chat history on all
  surfaces: bottom anchoring (anchorTo: end), key-stable prepend
  preservation, and native iOS touch/momentum deferral live in the core
- Patch virtual-core to clamp the render range to real scroll bounds
  during transient adjustments
- Rows render in normal flow inside a translated wrapper so sticky user
  headers keep working; measurement snapshots cached per session
- Pre-write container height in scrollToFn so the browser cannot clamp
  anchor corrections to the stale height; hold the prepend anchor for up
  to 180 frames on mobile while fresh rows settle (cancelled by user
  input; desktop relies on core anchoring alone)
- Adaptive row-size estimate from per-session measured averages; disable
  reveal fade-in for virtualized history rows
- Mobile loads older history only through an explicit localized top
  button: no scroll-position trigger and no post-mount background
  prepend, so every insert happens from a resting state; a quiet-window
  hold defers any stray prepend commit while a touch gesture is active
- Desktop/VS Code keep the seamless scroll-up trigger and progressive
  background prepend
This commit is contained in:
Bohdan Triapitsyn
2026-07-03 18:43:40 +03:00
parent d71aec54db
commit 2bce38cfbb
18 changed files with 458 additions and 145 deletions
+1
View File
@@ -1279,6 +1279,7 @@ export const dict = {
'diffView.reviewDialog.actions.starting': 'Starting...',
'diffView.reviewDialog.toast.noSessionDirectory': 'Session directory is unavailable',
'diffView.reviewDialog.toast.startFailed': 'Failed to start review flow',
'chat.history.loadOlder': 'Load older messages',
'chat.autoReview.title': 'Code review loop is running',
'chat.autoReview.status.waitingForReviewer': 'Waiting for reviewer',
'chat.autoReview.status.waitingForImplementer': 'Waiting for implementer',
+1
View File
@@ -1245,6 +1245,7 @@ export const dict: Record<I18nKey, string> = {
'diffView.reviewDialog.actions.starting': 'Iniciando...',
'diffView.reviewDialog.toast.noSessionDirectory': 'El directorio de la sesión no está disponible',
'diffView.reviewDialog.toast.startFailed': 'No se pudo iniciar el flujo de revisión',
'chat.history.loadOlder': 'Cargar mensajes anteriores',
'chat.autoReview.title': 'El ciclo de revisión de código está en curso',
'chat.autoReview.status.waitingForReviewer': 'Esperando al revisor',
'chat.autoReview.status.waitingForImplementer': 'Esperando al implementador',
+1
View File
@@ -1113,6 +1113,7 @@ export const dict = {
'diffView.reviewDialog.actions.starting': 'Démarrage...',
'diffView.reviewDialog.toast.noSessionDirectory': 'Le dossier de session est indisponible',
'diffView.reviewDialog.toast.startFailed': 'Impossible de démarrer le flux de revue',
'chat.history.loadOlder': 'Charger les messages précédents',
'chat.autoReview.title': 'La boucle de revue de code est en cours',
'chat.autoReview.status.waitingForReviewer': 'En attente du reviewer',
'chat.autoReview.status.waitingForImplementer': 'En attente de limplémenteur',
+1
View File
@@ -1284,6 +1284,7 @@ export const dict: Record<I18nKey, string> = {
'diffView.hunk.discardTitle': 'ハンク{index}を破棄',
'diffView.hunk.unavailable': 'このハンクはもう利用できません。差分を更新してからもう一度お試しください。',
'diffView.hunk.unsupported': '個別のハンクのステージングはこのランタイムではサポートされていません。',
'chat.history.loadOlder': '以前のメッセージを読み込む',
'chat.autoReview.title': 'コードレビューループが実行中です',
'chat.autoReview.status.waitingForReviewer': 'レビュアーを待機中',
'chat.autoReview.status.waitingForImplementer': '実装者を待機中',
+1
View File
@@ -1282,6 +1282,7 @@ export const dict: Record<I18nKey, string> = {
'diffView.reviewDialog.actions.starting': '시작 중...',
'diffView.reviewDialog.toast.noSessionDirectory': '세션 디렉터리를 사용할 수 없습니다',
'diffView.reviewDialog.toast.startFailed': '리뷰 흐름을 시작하지 못했습니다',
'chat.history.loadOlder': '이전 메시지 불러오기',
'chat.autoReview.title': '코드 리뷰 루프 실행 중',
'chat.autoReview.status.waitingForReviewer': '리뷰어를 기다리는 중',
'chat.autoReview.status.waitingForImplementer': '구현 에이전트를 기다리는 중',
+1
View File
@@ -1481,6 +1481,7 @@ export const dict: Record<I18nKey, string> = {
'diffView.reviewDialog.actions.starting': 'Uruchamianie...',
'diffView.reviewDialog.toast.noSessionDirectory': 'Katalog sesji jest niedostępny',
'diffView.reviewDialog.toast.startFailed': 'Nie udało się uruchomić flow review',
'chat.history.loadOlder': 'Wczytaj starsze wiadomości',
'chat.autoReview.title': 'Pętla code review trwa',
'chat.autoReview.status.waitingForReviewer': 'Oczekiwanie na reviewera',
'chat.autoReview.status.waitingForImplementer': 'Oczekiwanie na implementatora',
@@ -1245,6 +1245,7 @@ export const dict: Record<I18nKey, string> = {
'diffView.reviewDialog.actions.starting': 'Iniciando...',
'diffView.reviewDialog.toast.noSessionDirectory': 'O diretório da sessão está indisponível',
'diffView.reviewDialog.toast.startFailed': 'Falha ao iniciar o fluxo de revisão',
'chat.history.loadOlder': 'Carregar mensagens anteriores',
'chat.autoReview.title': 'O ciclo de revisão de código está em andamento',
'chat.autoReview.status.waitingForReviewer': 'Aguardando o revisor',
'chat.autoReview.status.waitingForImplementer': 'Aguardando o implementador',
+1
View File
@@ -1245,6 +1245,7 @@ export const dict: Record<I18nKey, string> = {
'diffView.reviewDialog.actions.starting': 'Запуск...',
'diffView.reviewDialog.toast.noSessionDirectory': 'Директорія сесії недоступна',
'diffView.reviewDialog.toast.startFailed': 'Не вдалося запустити review flow',
'chat.history.loadOlder': 'Завантажити ще',
'chat.autoReview.title': 'Цикл код-ревʼю триває',
'chat.autoReview.status.waitingForReviewer': 'Очікуємо ревʼювера',
'chat.autoReview.status.waitingForImplementer': 'Очікуємо імплементатора',
@@ -1245,6 +1245,7 @@ export const dict: Record<I18nKey, string> = {
'diffView.reviewDialog.actions.starting': 'Starting...',
'diffView.reviewDialog.toast.noSessionDirectory': 'Session directory is unavailable',
'diffView.reviewDialog.toast.startFailed': 'Failed to start review flow',
'chat.history.loadOlder': '加载更早的消息',
'chat.autoReview.title': '代码审查循环正在运行',
'chat.autoReview.status.waitingForReviewer': '等待审查者',
'chat.autoReview.status.waitingForImplementer': '等待实现者',
@@ -1255,6 +1255,7 @@ export const dict: Record<I18nKey, string> = {
'diffView.reviewDialog.actions.starting': 'Starting...',
'diffView.reviewDialog.toast.noSessionDirectory': 'Session directory is unavailable',
'diffView.reviewDialog.toast.startFailed': 'Failed to start review flow',
'chat.history.loadOlder': '載入更早的訊息',
'chat.autoReview.title': '程式碼審查循環執行中',
'chat.autoReview.status.waitingForReviewer': '等待審查者',
'chat.autoReview.status.waitingForImplementer': '等待實作者',