inspector/clients/web/src/hooks/useSchemaFindingsExpanded.ts
cliffhall db122f53db feat(web): collapse the schema-portability section by default (#2205)
The Tools tab rendered every schema-portability finding, always open, above
the argument form. On a server whose schemas are broadly unportable that put
the same wall of text ahead of every tool's first input — and the findings
address the server author, not the caller trying to fill the form.

The section now opens collapsed behind the same disclosure heading the Skills
pane's Conformance section uses, with an `N error(s), M warning(s)` badge. The
expand choice is global rather than per tool (`useSchemaFindingsExpanded`,
stored as `inspector.schemaFindings.expanded`): this panel is reused across
selections, so a per-tool disclosure would re-collapse on every click and
reproduce the same scrolling. The badge stays visible while collapsed and the
tool-list severity icon is untouched, so nothing about a tool's standing is
hidden by the closed state.

Two neighbouring fixes on the same panel:

- `JsonEditor` frames Ace in a bordered `Paper`. Ace paints its background edge
  to edge with nothing around it, so "Edit as JSON" replaced a column of
  bordered inputs with one borderless slab.
- `ToolDetailPanel`'s body `ScrollArea` overrode the app-wide `type="scroll"`
  default with `type="auto"`, parking a permanent bar down the side of any tool
  whose form is taller than the panel. `offsetScrollbars` stays, so the form
  does not shift sideways when the bar fades in.

`test-servers/configs/unportable-schemas-many-http.json` reproduces the
original complaint: the same four presets as `unportable-schemas-http.json`
carrying 26 findings rather than 5.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DU3QYnc95g6EM4vo3hYNet
Signed-off-by: cliffhall <cliff@futurescale.com>
2026-09-06 23:52:34 -04:00

54 lines
2.2 KiB
TypeScript

import { useLocalStorage } from "@mantine/hooks";
/**
* Whether the tool detail panel's schema-portability section is expanded
* (#2205).
*
* The preference is **global rather than per tool**, and that is the whole
* point of the issue: the findings sit above the argument form, so on a server
* whose schemas are broadly unportable every tool selection put a wall of
* identical text between the user and the first input — 26 findings across
* four tools in `unportable-schemas-many-http.json`, none of which the caller
* needs in order to fill the form. A per-tool disclosure would have to be
* re-collapsed on every selection, which is the same scrolling by another
* route.
*
* Collapsed is the default. Nothing is lost by it: the summary line still
* names the counts, the tool list still carries its per-tool severity icon,
* and one click re-opens the detail — for the whole session and every later
* one, since the choice persists.
*/
const SCHEMA_FINDINGS_EXPANDED_DEFAULT = false;
/** localStorage key. Shares the `inspector.<kind>.<scope>` namespace the other
* UI preferences use, so the whole group is easy to inspect or clear in bulk. */
export const SCHEMA_FINDINGS_EXPANDED_KEY = "inspector.schemaFindings.expanded";
/**
* Stores the boolean as `"true"` / `"false"` rather than Mantine's default
* `JSON.stringify`, matching the sort/compact adapters in `InspectorView`:
* the persisted value stays human-readable, and anything else — a manual edit,
* a value written by an older build — clamps back to the default instead of
* silently coercing to `false`.
*/
function deserialize(raw: string | undefined): boolean {
if (raw === "true") return true;
if (raw === "false") return false;
return SCHEMA_FINDINGS_EXPANDED_DEFAULT;
}
function serialize(value: boolean): string {
return value ? "true" : "false";
}
export function useSchemaFindingsExpanded() {
return useLocalStorage<boolean>({
key: SCHEMA_FINDINGS_EXPANDED_KEY,
defaultValue: SCHEMA_FINDINGS_EXPANDED_DEFAULT,
deserialize,
serialize,
// Read synchronously on first render — SPA only, no SSR — so a persisted
// "expanded" does not flash through the collapsed default.
getInitialValueInEffect: false,
});
}