feat(sort): add SortToggle for Logs, History, and Network (#1371) (#1372)

* feat(sort): add SortToggle for Logs, History, and Network (#1371)

Introduces a reusable SortToggle element and wires it into the Logs,
History, and Network screens so users can flip chronological order.
Selection is persisted per-screen to localStorage under the
inspector.sortDirection.* namespace, with invalid stored values falling
back to the newest-first default.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(logs): move auto-scroll checkbox left of the sort selector

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(logs): remove auto-scroll checkbox; ListToggle left of SortToggle

- Drops the Logs auto-scroll checkbox and its plumbing through
  LoggingScreen / InspectorView.
- Moves the ListToggle to the left of the SortToggle on the History
  and Network panel toolbars.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(sort): address review on #1372

- Hydrate sort direction synchronously (getInitialValueInEffect: false)
  to remove the one-frame newest-first flicker on first paint.
- Drop the redundant `[...matched].sort()` copy across the three panels;
  `.filter()` already returns a fresh array.
- Document why serializeSortDirection bypasses Mantine's default JSON
  stringify (raw enum value in storage).
- MemoryStorage.getItem: collapse `has` + `!` to `?? null`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(history): default the request list to collapsed for parity with Network

The History list opened expanded while Network opened collapsed; align
History with Network so both panels start in the same compact view.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(history,network): persist list compact state to localStorage

Lifts the per-panel `compact` flag out of HistoryListPanel /
NetworkStreamPanel and into InspectorView's localStorage hooks, mirroring
the sort-direction shape:

  inspector.listCompact.history
  inspector.listCompact.network

Defaults to collapsed (compact=true) on both, with a corrupted-value
deserializer that clamps unknown strings back to the default.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(servers,resources): persist ListToggle state to localStorage

Lifts the per-screen compact preference out of ServerListScreen and
ResourceControls into InspectorView's localStorage hooks, matching the
History/Network shape:

  inspector.listCompact.servers
  inspector.listCompact.resources

Servers and Resources both default to expanded (compact=false). For
ResourceControls the displayed compact is still derived from openSections
(so per-section accordion clicks still work locally), and only explicit
ListToggle clicks update the persisted preference — the handler computes
the next compact value from the click outcome rather than blindly
flipping, so a half-open accordion still records the right post-state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(sort-toggle): switch to icon button matching ListToggle style

Replaces the Mantine Select with a subtle Button (or ActionIcon for the
subtle variant), using TbSortDescending2 for newest-first and
TbSortAscending2 for oldest-first. Clicking the button flips between the
two values.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(sort-toggle): restore Select with sort icon in rightSection

Reverts SortToggle to a Mantine Select with simplified labels ("Newest
First" / "Oldest First") and the corresponding TbSortDescending2 /
TbSortAscending2 icon in the rightSection, replacing the default chevron.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(sort-toggle): swap icons so they match label direction

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(sort-toggle): use down arrow for newest, up arrow for oldest

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(inspector-view): extract useSortDirection / useListCompact

Collapses seven near-identical useLocalStorage call sites in
InspectorView into two scoped helpers. Each helper carries the shared
namespace, serialize/deserialize adapters, and getInitialValueInEffect:
false; the call sites now read as "this screen has sort persisted to
this scope" rather than re-stating the storage shape every time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(sort-toggle): apply prettier formatting

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cliff Hall 2026-05-29 14:47:19 -04:00 • committed by GitHub
commit dd0f5de820
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -28,6 +28,13 @@ export interface ResourceControlsProps {
onSelectUri: (uri: string) => void;
onSelectTemplate: (uriTemplate: string) => void;
onUnsubscribeResource: (uri: string) => void;
/**
* Persisted preference for the ListToggle. Seeds initial accordion state
* on mount; the user can still toggle individual sections during a session
* without affecting this value. Only an explicit ListToggle click updates it.
*/
compact: boolean;
onCompactChange: (next: boolean) => void;
}
function panelMaxHeight(openCount: number): string {
@ -52,6 +59,8 @@ export function ResourceControls({
onSelectUri,
onSelectTemplate,
onUnsubscribeResource,
compact: initialCompact,
onCompactChange,
}: ResourceControlsProps) {
const [searchText, setSearchText] = useState("");
const query = searchText.toLowerCase();
@ -74,17 +83,27 @@ export function ResourceControls({
s.resource.uri.toLowerCase().includes(query),
);
const defaultOpen = [
...(filteredResources.length > 0 ? ["resources"] : []),
...(filteredTemplates.length > 0 ? ["templates"] : []),
...(filteredSubscriptions.length > 0 ? ["subscriptions"] : []),
];
const allSections = ["resources", "templates", "subscriptions"];
const [openSections, setOpenSections] = useState<string[]>(defaultOpen);
// Seed the accordion from the persisted preference: empty when the user
// last left the panel compact, all three open when they explicitly
// expanded. Per-section accordion clicks during a session update the
// local list but don't change the persisted preference.
const [openSections, setOpenSections] = useState<string[]>(
initialCompact ? [] : [...allSections],
);
const allExpanded = openSections.length === allSections.length;
const maxHeight = panelMaxHeight(openSections.length);
function handleToggleList() {
// Compute the next compact value from what the click will produce so a
// half-open accordion (user toggled a single section) still persists the
// right preference: clicking "expand all" should record `compact=false`
// even if the visible state was already partially expanded.
const nextCompact = allExpanded;
setOpenSections(nextCompact ? [] : [...allSections]);
onCompactChange(nextCompact);
}
return (
<Stack gap="sm">
<Group justify="space-between">
@ -98,10 +117,7 @@ export function ResourceControls({
value={searchText}
onChange={(e) => setSearchText(e.currentTarget.value)}
/>
<ListToggle
compact={!allExpanded}
onToggle={() => setOpenSections(allExpanded ? [] : [...allSections])}
/>
<ListToggle compact={!allExpanded} onToggle={handleToggleList} />
</Group>
<Accordion multiple value={openSections} onChange={setOpenSections}>
<Accordion.Item value="resources">