style(web): bold the "Secrets:" prefix in the settings-modal footers

Weights the label so the band reads as a labelled fact rather than one
run of prose — the eye lands on what the line is about before reading
which store it names.

Rendered as a `span` inside the existing `Text` rather than as a sibling
in the `Group`: `Text` renders a `<p>`, so a nested one would be a `<p>`
inside a `<p>` (invalid, and the browser closes the outer paragraph
early), and a sibling would take the Group's `gap` and sit too far from
the value it labels. `inherit` keeps it at the label's own size.

Splitting the text is why the assertions move off `getByText`: it matches
a string against an element's *direct* text nodes, so "Secrets: OS
keychain" now reads as two fragments even though it renders as one line.
The unit tests, both modal tests, and the four stories assert
`toHaveTextContent` on the band instead, which reads the full subtree.

Verified in the running dev server: prefix 700, store name 500, both at
the same 12px.

Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-22 13:31:04 -04:00
commit 6460d1804a
5 changed files with 58 additions and 53 deletions

View file

@ -22,13 +22,11 @@ type Story = StoryObj<typeof SecretStorageFooter>;
export const Keychain: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(
await canvas.findByText("Secrets: OS keychain"),
).toBeInTheDocument();
await expect(canvas.getByTestId("secret-storage-footer")).toHaveAttribute(
"data-tone",
"neutral",
);
// Asserted on the band's full text: the "Secrets:" prefix is its own
// `<span>`, which a `findByText` string match would not see as one string.
const band = await canvas.findByTestId("secret-storage-footer");
await expect(band).toHaveTextContent("Secrets: OS keychain");
await expect(band).toHaveAttribute("data-tone", "neutral");
},
};
@ -46,9 +44,8 @@ export const EncryptedFile: Story = {
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(
await canvas.findByText("Secrets: File (encrypted)"),
).toBeInTheDocument();
const band = await canvas.findByTestId("secret-storage-footer");
await expect(band).toHaveTextContent("Secrets: File (encrypted)");
await expect(
canvas.getByText("/home/node/.mcp-inspector/secrets.json"),
).toBeInTheDocument();
@ -70,13 +67,9 @@ export const UnencryptedFile: Story = {
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(
await canvas.findByText("Secrets: File (unencrypted)"),
).toBeInTheDocument();
await expect(canvas.getByTestId("secret-storage-footer")).toHaveAttribute(
"data-tone",
"warn",
);
const band = await canvas.findByTestId("secret-storage-footer");
await expect(band).toHaveTextContent("Secrets: File (unencrypted)");
await expect(band).toHaveAttribute("data-tone", "warn");
},
};
@ -86,9 +79,8 @@ export const InMemory: Story = {
args: { info: { kind: "memory", reason: "fallback", durable: false } },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(
await canvas.findByText("Secrets: Memory (this session only)"),
).toBeInTheDocument();
const band = await canvas.findByTestId("secret-storage-footer");
await expect(band).toHaveTextContent("Secrets: Memory (this session only)");
await expect(
canvas.getByText(/lost when the Inspector exits/),
).toBeInTheDocument();

View file

@ -25,25 +25,25 @@ const memory: SecretStorageInfo = {
describe("SecretStorageFooter", () => {
it("names the keychain with no caveat and no warning tone", () => {
renderWithMantine(<SecretStorageFooter info={keyring} />);
expect(screen.getByText("Secrets: OS keychain")).toBeInTheDocument();
expect(screen.getByTestId("secret-storage-footer")).toHaveAttribute(
"data-tone",
"neutral",
);
// Asserted on the band's full text rather than with `getByText`: the
// "Secrets:" prefix is its own `<span>`, and testing-library matches a
// string against an element's *direct* text nodes only, so the label now
// reads as two fragments even though it renders as one line.
const band = screen.getByTestId("secret-storage-footer");
expect(band).toHaveTextContent("Secrets: OS keychain");
expect(band).toHaveAttribute("data-tone", "neutral");
});
it("shows the path for an encrypted file, quietly", () => {
// Nothing is wrong here, so the band must not shout — the path is the
// useful part and the tone stays neutral.
renderWithMantine(<SecretStorageFooter info={encryptedFile} />);
expect(screen.getByText("Secrets: File (encrypted)")).toBeInTheDocument();
const band = screen.getByTestId("secret-storage-footer");
expect(band).toHaveTextContent("Secrets: File (encrypted)");
expect(
screen.getByText("/home/node/.mcp-inspector/secrets.json"),
).toBeInTheDocument();
expect(screen.getByTestId("secret-storage-footer")).toHaveAttribute(
"data-tone",
"neutral",
);
expect(band).toHaveAttribute("data-tone", "neutral");
});
it("renders the plaintext caveat inline, not only on hover", () => {
@ -51,28 +51,22 @@ describe("SecretStorageFooter", () => {
// to hover to discover is not a warning. The caveat takes the visible
// slot, displacing the path (which stays in the tooltip).
renderWithMantine(<SecretStorageFooter info={plaintextFile} />);
expect(screen.getByText("Secrets: File (unencrypted)")).toBeInTheDocument();
const band = screen.getByTestId("secret-storage-footer");
expect(band).toHaveTextContent("Secrets: File (unencrypted)");
expect(
screen.getByText(/Set MCP_INSPECTOR_SECRET_KEY to encrypt them/),
).toBeInTheDocument();
expect(screen.getByTestId("secret-storage-footer")).toHaveAttribute(
"data-tone",
"warn",
);
expect(band).toHaveAttribute("data-tone", "warn");
});
it("warns that an in-memory store loses secrets on exit", () => {
renderWithMantine(<SecretStorageFooter info={memory} />);
expect(
screen.getByText("Secrets: Memory (this session only)"),
).toBeInTheDocument();
const band = screen.getByTestId("secret-storage-footer");
expect(band).toHaveTextContent("Secrets: Memory (this session only)");
expect(
screen.getByText(/lost when the Inspector exits/),
).toBeInTheDocument();
expect(screen.getByTestId("secret-storage-footer")).toHaveAttribute(
"data-tone",
"warn",
);
expect(band).toHaveAttribute("data-tone", "warn");
});
it("renders nothing when the backend didn't report a store", () => {

View file

@ -47,6 +47,22 @@ const FooterLabel = Text.withProps({
variant: "nowrap",
});
// "Secrets:" carries the heavier weight so the band reads as a labelled fact
// rather than as one run of prose — the eye lands on what the line is about
// before reading which store it names.
//
// A `span` inside the label rather than a sibling in the `Group`: `Text`
// renders a `<p>`, so a nested one would be a `<p>` inside a `<p>` (invalid,
// and the browser would close the outer paragraph early), and a sibling would
// take the Group's `gap` and sit too far from the value it labels. `inherit`
// keeps it at the label's own size and line height instead of Mantine's `Text`
// defaults.
const FooterLabelPrefix = Text.withProps({
span: true,
inherit: true,
fw: 700,
});
// The detail — a path, or the caveat sentence — is the part allowed to lose
// characters, since the label before it carries the answer.
//
@ -128,7 +144,10 @@ export function SecretStorageFooter({ info }: SecretStorageFooterProps) {
`.withProps()` rule, and it takes `color` as a prop rather than a
style. */}
<Icon size={ICON_SIZE} color={color} aria-hidden />
<FooterLabel c={color}>Secrets: {secretStorageLabel(info)}</FooterLabel>
<FooterLabel c={color}>
<FooterLabelPrefix>Secrets:</FooterLabelPrefix>{" "}
{secretStorageLabel(info)}
</FooterLabel>
{detail && <FooterDetail c={color}>{detail}</FooterDetail>}
</FooterBand>
);

View file

@ -492,13 +492,11 @@ describe("ClientSettingsModal", () => {
}}
/>,
);
expect(
screen.getByText("Secrets: File (unencrypted)"),
).toBeInTheDocument();
expect(screen.getByTestId("secret-storage-footer")).toHaveAttribute(
"data-tone",
"warn",
);
// Full-text assertion: the "Secrets:" prefix is its own `<span>`, which
// `getByText`'s direct-text-node matching would not see as one string.
const band = screen.getByTestId("secret-storage-footer");
expect(band).toHaveTextContent("Secrets: File (unencrypted)");
expect(band).toHaveAttribute("data-tone", "warn");
});
it("renders no footer when the backend didn't report a store", () => {

View file

@ -920,9 +920,11 @@ describe("ServerSettingsModal", () => {
}}
/>,
);
expect(
screen.getByText("Secrets: Memory (this session only)"),
).toBeInTheDocument();
// Full-text assertion: the "Secrets:" prefix is its own `<span>`, which
// `getByText`'s direct-text-node matching would not see as one string.
expect(screen.getByTestId("secret-storage-footer")).toHaveTextContent(
"Secrets: Memory (this session only)",
);
});
it("renders no footer when the backend didn't report a store", () => {