agent-ecosystem/docs/research/lead-thought-msg-scroll.md
iliya 71143db3ac feat: update cross-team messaging protocol and enhance team provisioning
- Refactored cross-team message formatting to use a canonical XML-like tag structure for improved clarity and consistency.
- Introduced new attributes for cross-team messages, including 'from', 'depth', 'conversationId', and 'replyToConversationId'.
- Enhanced TeamProvisioningService to manage pending cross-team reply expectations, ensuring proper message handling and relay.
- Updated UI components to reflect changes in cross-team messaging, including stripping metadata from displayed messages.
- Added tests to validate new cross-team message handling and ensure proper functionality across services.
2026-03-10 14:48:55 +02:00

89 lines
No EOL
8.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## Findings: реверс порядка lead thoughts (свежие вверху)
### Затронутые файлы
| Файл | Что менять |
|------|------------|
| `LeadThoughtsGroup.tsx` | Основной компонент. Строка 471: `chronologicalThoughts = [...thoughts].reverse()` — убрать reverse (thoughts уже newest-first). Логика автоскрола внутри `LeadThoughtsGroupRow` (строки 570-658). |
| `ActivityTimeline.tsx` | Pinned thought group (строка 322). Порядок messages — newest-first (desc), thoughts группируются через `groupTimelineItems()`. |
| `collapseState.ts` | `findNewestMessageIndex()` ищет первый `type: 'message'` — при реверсе внутри группы не затронут. |
| `AnimatedHeightReveal.tsx` | Не требует изменений — анимирует высоту, а не направление. |
### Архитектура текущего скрола thoughts
**LeadThoughtsGroupRow** имеет свой собственный скрол-контейнер (строки 780-807):
- `scrollRef` — div с `maxHeight: 200px`, `overflowY: auto`
- `contentRef` — внутренний div с thoughts
- Автоскрол к **низу** (`queueScrollSync('bottom')`) — потому что newest внизу
- `isUserScrolledUpRef` — трекает, отскроллил ли юзер вверх
- `distanceFromBottomRef` — сохраняет позицию для `preserve` mode
- `handleScroll` (строка 652) — обновляет `isUserScrolledUpRef` через `AUTO_SCROLL_THRESHOLD = 30px`
- `handleCollapse` (строка 661) — при Show Less скроллит к `scrollHeight` (к низу)
- `syncScrollableBody` (строка 598) — оркестрирует: force bottom / preserve / noop
**Рендеринг** (строка 795): `chronologicalThoughts.map()` — oldest-first, newest в конце. Анимация `shouldAnimate` только для последнего (newest).
### Что нужно изменить для реверса
1. **Убрать `.reverse()` на строке 471** — thoughts уже newest-first, рендерить как есть
2. **Перевернуть автоскрол**: вместо scroll-to-bottom → scroll-to-top (`scrollTop = 0`)
3. **Анимация нового thought**: `shouldAnimate` для `idx === 0` (вместо `idx === chronologicalThoughts.length - 1`)
4. **`isUserScrolledUpRef`** → переименовать в `isUserScrolledDownRef`, проверять расстояние от **верха** (`scrollTop > threshold`)
5. **`queueScrollSync`** — `mode: 'top'` вместо `'bottom'` (`scrollTop = 0`), preserve mode: `scrollTop = distanceFromTopRef.current`
6. **Show More кнопка** — сейчас внизу (ChevronDown). При реверсе: newest вверху, кнопка "Show more" нужна внизу для загрузки старых thoughts — по сути остаётся на месте
7. **Show Less** (`handleCollapse`) — скроллит к верху (`scrollTop = 0`) вместо `scrollHeight`
8. **Divider timestamps** (строка 355-360): `showDivider` при `idx > 0` — работает корректно и при реверсе
### Edge Cases
1. **Новый thought приходит во время скрола вниз к старым** — если юзер прокрутил вниз (к старым), новый thought добавляется вверху. Нужно сохранить `scrollTop` позицию. Решение: `preserve` mode отслеживает `distanceFromTop` вместо `distanceFromBottom`.
2. **Первый thought в пустой группе** — скролл не нужен (нет overflow), анимация fade-in для первого элемента.
3. **Переход collapsed → expanded**`setExpanded(true)` убирает maxHeight. При реверсе scrollTop=0 уже наверху, без скачков. Проблем нет.
4. **Expanded → collapsed (Show Less)** — сейчас `handleCollapse` скроллит к scrollHeight. При реверсе → скроллить к 0. Плюс `scrollIntoView` на контейнер — остаётся как есть.
5. **Real-time streaming** — thoughts приходят через InboxMessage live updates. Каждый новый thought prepend-ится в начало массива `thoughts[]` (newest-first). При реверсе рендера: новый появляется вверху, анимируется LeadThoughtItem с `shouldAnimate`. Ключевой риск: `AnimatedHeightReveal` wrapper расширяется вниз (grid-template-rows: 0fr→1fr). При insert-е вверху контейнер сдвигает всё вниз → **layout shift**. Mitigation: CSS `overflow-anchor: none` уже стоит (строка 790). Но для items внутри scroll-контейнера нужно `overflow-anchor: auto` на последнем видимом элементе. **Это главный технический риск.**
6. **`getThoughtGroupKey`** (строка 67-70) — использует oldest thought для стабильного ключа. При реверсе rendering порядок меняется, но key остаётся тот же. Проблем нет.
7. **ResizeObserver** (строка 632-637) — наблюдает за contentRef. При реверсе content растёт вверху → ResizeObserver сработает, вызовет syncScrollableBody. Нужно убедиться что `preserve` mode корректно считает offset от верха.
8. **`isBodyVisible` toggle** (collapse mode из ActivityTimeline) — скрывает/показывает body. При реверсе: после re-show нужно скроллить к top (не bottom). Затронуто useLayoutEffect на строке 625-638.
### Аналоги в проекте для референса
- `DisplayItemList.tsx:107` — использует `flex-col-reverse` для newest-first. Простой подход, но не подходит для нашего случая (у нас свой scroll container с автоскролом).
- `CliLogsRichView.tsx:376``[...entries].reverse()` для newest-first порядка.
### Предложенный план реализации
1. В `LeadThoughtsGroupRow`: убрать `chronologicalThoughts` reverse, рендерить `thoughts` напрямую (newest-first)
2. Перевернуть scroll-sync логику: auto-scroll к `scrollTop=0`, preserve через `distanceFromTop`
3. Обновить `shouldAnimate` для `idx === 0`
4. Обновить `handleCollapse``scrollTop = 0`
5. Show More/Show Less кнопки: Show More внизу (для старых thoughts) — без изменений. Show Less — без изменений.
6. Тестировать layout shift при live streaming
### Оценки
**Сложность реализации: 5/10**
- Основная логика сосредоточена в одном файле (LeadThoughtsGroup.tsx)
- Scroll-sync перевернуть — умеренно сложно (6 точек изменения: queueScrollSync, handleScroll, handleCollapse, syncScrollableBody, useLayoutEffect, shouldAnimate)
- Нет зависимости от внешнего useAutoScrollBottom — LeadThoughtsGroupRow имеет свой собственный scroll management
- Не нужно трогать groupTimelineItems() или ActivityTimeline
**Уверенность в оценке: 8/10**
- Код хорошо изолирован — весь scroll management внутри одного компонента
- Единственный серьёзный риск — layout shift при real-time prepend (edge case #5)
- CSS overflow-anchor может не работать идеально для insert-вверх в scroll container
### Оценка рисков
| Риск | Вероятность | Последствие | Mitigation |
|------|-------------|-------------|------------|
| Layout shift при live streaming | Средняя | Визуальные скачки | overflow-anchor + manual scrollTop adjustment |
| Broken Show More/Less | Низкая | UX деградация | Простой фикс scrollTop |
| Regression в collapsed mode | Низкая | Thoughts не видны | Не затрагивает collapse logic |
| Browser inconsistency overflow-anchor | Низкая | Скачки в Safari | Fallback: manual scroll compensation |