You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 535d9f6
Browse filesBrowse the repository at this point in the historyBrowse files
Add filtering functionality to settings lists. Introduce SettingsFilterField and SettingsListFilter components, update related styles, logic, and tests.
Copy file name to clipboardExpand all lines: docs/12-ux-decisions.md
+8-1Lines changed: 8 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -243,7 +243,7 @@ Duplicate active branch has not been implemented: it makes sense to add it toget
243
243
- Разграничение с уже существовавшим `_scrollMessagesToBottom` (форс-скролл вниз после отправки/получения сообщения): раньше `SelectChatAsync`/`SelectBranchTreeItemAsync` тоже безусловно выставляли этот флаг при любом переключении, отчего восстановление позиции пришлось бы либо игнорировать, либо гадать с перезаписью. Вместо этого переключения чата/ветки теперь выставляют отдельный `_pendingScrollRestoreKey`, а `OnAfterRenderAsync` обрабатывает его только если `_scrollMessagesToBottom` НЕ взведён в этом же рендере — явное "только что отправили/получили" всегда должно побеждать протухший ожидающий рестор.
244
244
- Автоскролл вниз при поступлении нового содержимого стал условным: `OnRunSnapshot` больше не взводит `_scrollMessagesToBottom`, а выставляет отдельный `_followNewMessages`, и `MessageFeed.FollowNewContentAsync()` прокручивает ленту, ТОЛЬКО если пользователь уже находится у нижнего края (порог 80px, `messageScroll.js`). Прежнее безусловное «прыгнуть в конец на каждый snapshot» делало невозможным чтение старых сообщений, пока агент работает: лента дёргалась вниз на каждый результат инструмента. Приоритет в `OnAfterRenderAsync` — `_scrollMessagesToBottom` (явное «я только что отправил») > `_pendingScrollRestoreKey` (восстановление позиции при переключении чата/ветки) > `_followNewMessages`: решение о позиции, принятое явным действием пользователя, всегда важнее пришедшего в том же рендере снапшота.
245
245
- «Пользователь внизу» вычисляется НЕ в момент рендера, а из его собственной прокрутки, записанной по ходу дела: к моменту, когда Blazor дорисовал новое сообщение, расстояние до низа уже включает это сообщение, и померить постфактум нечего. Поэтому флаг живёт в `messageScroll.js` и обновляется в обработчике `scroll`, а C# узнаёт о смене состояния через `[JSInvokable] OnPinnedChanged` (только на переходах, не на каждом событии). В `MessageFeed.ShouldRender` добавлены `_pinned`/`_newBelow` — иначе `StateHasChanged` из JS-колбэка не дошёл бы до DOM, поскольку компонент перерисовывается только при изменении параметров.
246
-
- Кнопка возврата (`.messages-jump`) показывается, только если пользователь НЕ внизу И внизу есть что-то новое (`_newBelow`) либо идёт генерация. Постоянная кнопка «вниз» дублировала бы полосу маркеров истории слева, которая для навигации по транскрипту уже есть; эта кнопка говорит другое — «то, чего вы ждали, появилось не там, где вы смотрите». Содержимое — только шеврон и акцентная точка, без текста и без счётчика: в диалоге с одним собеседником счёт почти всегда 1 либо растёт за счёт служебных tool-сообщений и ничего не сообщает. Точка пульсирует только пока ответ пишется. Безусловно возвращают вниз и включают слежение обратно: отправка сообщения, клик по кнопке и клавиша `End` (кроме случая, когда фокус в поле ввода — там `End` значит «конец строки»).
246
+
- Кнопка возврата (`.messages-jump`) показывается, только если пользователь НЕ внизу И внизу есть что-то новое (`_newBelow`) либо идёт генерация. Постоянная кнопка «вниз» дублировала бы полосу маркеров истории слева, которая для навигации по транскрипту уже есть; эта кнопка говорит другое — «то, чего вы ждали, появилось не там, где вы смотрите». Содержимое — стрелка `arrow-down` и короткая подпись того, что ждёт внизу: `New message` (сообщение пришло) или `Writing…` (ответ ещё пишется), без счётчика: в диалоге с одним собеседником счёт почти всегда 1 либо растёт за счёт служебных tool-сообщений и ничего не сообщает. Акцентная точка рядом с подписью пульсирует только пока ответ пишется. Стрелка, а не шеврон, и плавающая акцентная «пилюля», а не строка в ленте — чтобы кнопку нельзя было спутать с `Hide steps` в конце раскрытого хода (см. [Compact turn view](18-compact-turn-view.md)). Безусловно возвращают вниз и включают слежение обратно: отправка сообщения, клик по кнопке и клавиша `End` (кроме случая, когда фокус в поле ввода — там `End` значит «конец строки»).
247
247
- Якорение прокрутки (`captureAnchor`/`restoreAnchor` в `messageScroll.js`) удерживает точку чтения, когда содержимое ВЫШЕ неё меняет высоту — раскрылась группа инструментов, дорисовался markdown. Якорь ищется не по сообщению целиком, а спуском до самого мелкого блока, всё ещё пересекающего верхнюю границу (глубина ограничена 5): раскрытие группы инструментов внутри читаемого сообщения не двигает верх самого сообщения, поэтому якорь на нём не удерживает ничего. Позиции берутся из живых `getBoundingClientRect()`, а не `offsetTop`, — на глубине offset-родитель у элементов разный, а расстояние до верхней кромки ленты везде значит одно и то же.
248
248
- Два решения в `messageScroll.js`, каждое подтверждено падением в браузере, а не рассуждением. Первое: обработчик `MutationObserver` работает НАПРЯМУЮ, без `requestAnimationFrame` (та же причина, что у `render()` в `historyMarkers.js`: rAF не гарантированно тикает в неактивном окне — с ним коррекция не срабатывала вовсе). Второе: у него НЕТ отсечки «высота не изменилась — выходим». Diff Blazor'а проходит через промежуточные состояния (блок удалён в одной пачке мутаций, добавлен в следующей), и вариант с отсечкой успевал скорректировать позицию по промежуточному состоянию, а пачку, которая вернула бы всё назад, пропускал — лента уезжала на сотни пикселей. Пересчёт дрейфа на каждую пачку самовосстанавливается, а при обычной дописке содержимого снизу дрейф равен нулю и ничего не делает.
249
249
- Вся persistence-логика (`ai-client.last-project.v1`/`project-context.v1`/`composer-drafts.v1`, три словаря, debounce черновика) вынесена из `Home.razor` в `IWorkspaceStateService`/`WorkspaceStateService` (`src/AI.Web/State/`), зарегистрирован как singleton через Pure.DI в `Composition.cs` — по тому же паттерну, что уже был у `IWorkspaceLayoutService`/`WorkspaceLayoutService` (`.Root<T>()` + `.Singleton<...>()`, конструктор с `IJSRuntime`). `Home.razor` теперь только вызывает методы сервиса (`InitializeAsync`/`LastProjectId`/`SetLastProjectIdAsync`/`GetProjectContext`/`SetProjectContextAsync`/`GetComposerDraft`/`QueueComposerDraftSave`) в нужных точках жизненного цикла — сам JSON/localStorage код и обе dictionary-кэша в razor-файле не живут. `GetScrollPositionKey()`/`GetComposerDraftKey()` остались в `Home.razor` (не в сервисе) — это чистое форматирование КЛЮЧА из текущего UI-состояния (`_selectedChat`/`_selectedProject`/`_branchLeafId`), сервис о структуре ключей знать не должен, только хранить значения по ним.
@@ -371,3 +371,10 @@ Duplicate active branch has not been implemented: it makes sense to add it toget
371
371
- The widget column is rendered by Blazor only while it is open, so its state stays in C# (`_chatWidgetsOpen`, saved in client settings). The shell carries it as `data-widgets="open|closed|unavailable"`, and the layout script asks for changes through `Home.SetChatWidgetsOpen`. It is not a class on `<main>` any more: a re-rendered class attribute wiped the classes the script sets there (`sidebar-collapsed`, `is-resizing`).
372
372
- Phone width (`<= 900px`; it was `<= 720px`, which left windows of 720–900px with the sidebar in the grid and the widget column floating over the chat) no longer stacks the sidebar above the chat. The conversation takes the whole screen and both panels are drawers over it, folded on every entry into phone width; the header toggle buttons and the dividers are hidden there. A swipe to the right opens the sidebar, a swipe to the left opens the widget column; the opposite swipe, a tap on the shade, Escape or choosing a chat, search, archive or a settings section closes the drawer. A mouse resting for 200ms within 12px of the left or right window edge brings that drawer out over the chat for as long as the pointer stays on it; 300ms after the pointer leaves it the drawer goes back, unless one of its menus is open. A drawer opened by a swipe stays until it is dismissed. Swipes that start in something that scrolls sideways, such as a code block, are left to it. Ctrl+B toggles the sidebar drawer.
373
373
- The wide-screen collapse state and widths are not touched by the phone layout and come back when the window widens again.
374
+
375
+
## Filtering the settings lists (2026-10-06)
376
+
377
+
- Connections, MCP, Memory and Skills share one filter field (`SettingsFilterField`) above the list. It is shown for every list that has rows, so it is always in the same place; an empty list goes without it unless it still holds a query.
378
+
- Every word of the query must occur, ignoring case and order, in what the row is about: a connection's name, URL, model and "good for"; a server's name, URL, command and status; a memory entry's title and text; a skill's name, id, description, source and aliases. The words are marked in the rows that remain, in the accent tint search results use.
379
+
- An MCP server is also kept by its tools: the row then says how many tools match, and opening it goes to its Tools page filtered by the same words. The Tools page uses the same field.
380
+
- The field shows `shown/total` while it filters. Ctrl+F focuses it, Enter opens the first remaining row, Escape clears the query and only an empty field lets Escape close the drawer. Adding an item clears the filter so the new row is visible.
Copy file name to clipboardExpand all lines: docs/18-compact-turn-view.md
+13Lines changed: 13 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -109,6 +109,19 @@ If the turn ends with an error, the row keeps `Failed after …` and shows no ba
109
109
110
110
The row is the click target. Hover highlights the row and shows a chevron; keyboard focus adds a frame. Click or Enter/Space toggles the section.
111
111
112
+
An expanded turn ends with a **Hide steps** control (`.turn-collapse-footer`), so a long turn can be folded from where the reader is. It mirrors the row it closes: it starts at the left edge of the column, uses the row's muted type, a `chevron-up` and the visible label, and a hairline after the label marks where the intermediate steps end. It stays in the flow and has no fill, border or shadow at rest. Collapsing from it keeps the content below in place.
113
+
114
+
It must not be confused with the jump to the latest message (`.messages-jump`, see [UX decisions](12-ux-decisions.md)), which is the opposite in every cue:
115
+
116
+
|| Hide steps | Jump to latest |
117
+
| --- | --- | --- |
118
+
| Belongs to | the transcript, one turn | the window |
119
+
| Position | in the flow, left edge of the column | floating, centred at the bottom of the feed |
| Icon |`chevron-up`: folds content |`arrow-down`: moves the view |
122
+
| Label |`Hide steps`|`New message` or `Writing…` with a pulsing dot |
123
+
| Shown | whenever a turn is expanded | only when something new is below and the feed is not at the bottom |
124
+
112
125
### Animation
113
126
114
127
The collapse is animated as an instant change without slide or fade, so a conversation does not appear to "twitch". Expanding today is a layout change that is rendered synchronously.
0 commit comments