feat(sort): add SortToggle for Logs, History, and Network (#1371)#1372
Conversation
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>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…oggle - 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>
|
@claude review |
|
Claude finished @cliffhall's task in 2m 44s —— View job Claude PR Review —
|
- 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>
|
Thanks for the review! Responses below, applied in 1730c8b. Suggestions1. Initial-mount flicker — 2. Conditional 3. Redundant 4. Hard-coded 190px Nits
Validate + unit + storybook all green after the changes. |
|
@claude review |
|
Claude finished @cliffhall's task in 2m 4s —— View job Claude PR Re-Review —
|
| Item | Status | Notes |
|---|---|---|
getInitialValueInEffect: false on all three useLocalStorage calls |
✅ | InspectorView.tsx:318,325,332. Comment at lines 307–312 captures the SPA / no-SSR rationale. |
Redundant [...matched].sort() collapsed |
✅ | LogStreamPanel.tsx:73, HistoryListPanel.tsx:86, NetworkStreamPanel.tsx:97 — all three carry the same "fresh array, in-place sort is safe" comment. Consistent. |
MemoryStorage.getItem simplified |
✅ | setup.ts:17–19 now this.store.get(key) ?? null. |
Comment on serializeSortDirection |
✅ | InspectorView.tsx:58–60 explains the override and the test-assertion benefit. |
| Conditional localStorage shim | 🟡 Deferred — sound reasoning (uniform substrate across CI/local/future Node). | |
| 190px hard-coded width | 🟡 Deferred — flagged in PR comments as a follow-up. |
Fresh observations on this revision
1. Three near-identical useLocalStorage blocks in InspectorView (trivial, optional)
InspectorView.tsx:313–333 repeats the same 6-line block three times, differing only in the scope key (logs / history / network). A tiny local helper would dry it up:
function useSortDirection(scope: "logs" | "history" | "network") {
return useLocalStorage<SortDirection>({
key: `inspector.sortDirection.${scope}`,
defaultValue: SORT_DEFAULT,
deserialize: deserializeSortDirection,
serialize: serializeSortDirection,
getInitialValueInEffect: false,
});
}…then const [logsSort, setLogsSort] = useSortDirection("logs"), etc. Not blocking — three repetitions sits right at the line where "extract" and "leave inline" are both defensible, and the current form keeps the storage adapter visible at the call site. Mention it only because if a fourth panel adopts sort later, this is the obvious moment.
2. isSortDirection and deserializeSortDirection validate the same union (nit, no action)
SortToggle.tsx:16 and InspectorView.tsx:54 both type-guard the same "oldest-first" | "newest-first" set. They live in different layers (component input vs. storage hydration) and don't need to share, but it's worth noting if the enum ever grows a third value — both call sites need to be updated. A single exported isSortDirection from SortToggle.tsx would centralize it; up to you whether the indirection is worth it.
3. Sort-then-reverse vs. directional comparator (stylistic)
LogStreamPanel.tsx:76–77:
.sort((a, b) => a.receivedAt.getTime() - b.receivedAt.getTime());
if (sortDirection === "newest-first") sorted.reverse();You could fold direction into the comparator and skip the reverse(), but the current form is arguably clearer — one canonical ascending sort, then a final flip. Leave as-is.
Things I liked (carried over + new)
- Source state is still untouched — sort lives in
useMemoon the rendered list. Clean separation. - Cross-tab sync comes for free via
useLocalStorage'sstorage-event listener — and per-screen keys mean Logs sync to Logs, History to History, etc., without interference. - Persistence round-trip test (
InspectorView.test.tsx:337–386) covers mount → flip → unmount → remount → restore. Good signal. - Corruption-fallback test (
InspectorView.test.tsx:388–409) directly asserts the deserializer clamps"garbage"back to"newest-first". Exactly the right shape. - Distinct aria-labels per panel (
Logs sort direction,History sort direction,Network sort direction) keep the a11y story clean when screens swap. - Test setup shim docstring (
setup.ts:5–8) accurately captures why the shim exists. Future readers won't be confused.
Verdict
LGTM. Previous review items are all addressed correctly; the remaining notes are all optional polish.
· branch: feat/1371-sort-toggle
…etwork 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>
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>
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>
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>
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>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
|
Thanks for the re-review! Applied in 2ef62cb. Fresh observations1. DRY the 2. Duplicate 3. Sort-then-reverse vs. directional comparator — Left as-is per your own note that the current form is arguably clearer. |
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
SortToggleelement (MantineSelectwith "Sort: Newest First" / "Sort: Oldest First").localStorageunderinspector.sortDirection.{logs,history,network}via@mantine/hooksuseLocalStorage. Invalid stored values fall back to thenewest-firstdefault so the toggle never renders in an unselectable state.localStorageshim in the unit-test setup because Node 22+ exposes an experimental placeholder that masks happy-dom's implementation when--localstorage-fileis not provided.Closes #1371.
Test plan
npm run validate(format, lint, build, unit tests + coverage)npm run test:storybook(323 stories pass under headless Chromium)inspector.sortDirection.logsin DevTools and confirm fallback to "Sort: Newest First"🤖 Generated with Claude Code