feat(web): monitoring column search, close-to-fullscreen, and open/close animation (#1616)

- Add a search box to MonitoringControls (right of the tabs). It's a single
  shared search kept across column tab switches, filtering whichever screen is
  shown. Embedded panels apply this text but still ignore the sidebar-only
  filters (levels/categories/directions/method), which have no control there.
- Close button now uses the "collapse sidebar" icon (TbLayoutSidebarRightCollapse),
  the mirror of the pin button's expand icon.
- Closing the column carries its selection to the primary view: the screen the
  user was watching becomes the active header tab (rather than snapping back to
  the pre-pin selection).
- Animate the column open/close: the handle + column slide in from the right and
  fade (reversing on close) via a Mantine Transition.
- HistoryListPanel: drop the redundant "History (N)" header when there are no
  pinned requests (lone unpinned section renders headerless).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
cliffhall 2026-07-08 20:16:15 -04:00
commit c5e064ff71

View file

@ -56,8 +56,11 @@ function matchesFilters(
entry: LogEntryData,
filterText: string,
visibleLevels: Record<LoggingLevel, boolean>,
// The embedded column exposes only the search box (no level toggles), so it
// applies the text filter but skips the level filter (#1616).
ignoreLevels: boolean,
): boolean {
if (!visibleLevels[entry.params.level]) return false;
if (!ignoreLevels && !visibleLevels[entry.params.level]) return false;
if (filterText) {
const term = filterText.toLowerCase();
const searchable =
@ -80,12 +83,11 @@ export function LogStreamPanel({
}: LogStreamPanelProps) {
const viewportRef = useScrollMemory("logs-stream");
const filteredEntries = useMemo(() => {
// The embedded column has no filter sidebar, so it shows the full stream
// rather than mirroring the full-size screen's live filter with no visible
// control to explain it (#1616). `.filter()` returns a fresh array, so
// sorting in-place is safe.
// The embedded column has only the search box (its level toggles live in the
// full-size sidebar), so it filters by text but ignores the level filter
// (#1616). `.filter()` returns a fresh array, so sorting in-place is safe.
const sorted = entries
.filter((e) => embedded || matchesFilters(e, filterText, visibleLevels))
.filter((e) => matchesFilters(e, filterText, visibleLevels, embedded))
.sort((a, b) => a.receivedAt.getTime() - b.receivedAt.getTime());
if (sortDirection === "newest-first") sorted.reverse();
return sorted;