From 6460d1804a6fd1d1900c8e552ec4e487763315e6 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Sat, 22 Aug 2026 13:31:04 -0400 Subject: [PATCH] style(web): bold the "Secrets:" prefix in the settings-modal footers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 `

`, so a nested one would be a `

` inside a `

` (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 --- .../SecretStorageFooter.stories.tsx | 32 ++++++---------- .../SecretStorageFooter.test.tsx | 38 ++++++++----------- .../SecretStorageFooter.tsx | 21 +++++++++- .../ClientSettingsModal.test.tsx | 12 +++--- .../ServerSettingsModal.test.tsx | 8 ++-- 5 files changed, 58 insertions(+), 53 deletions(-) diff --git a/clients/web/src/components/elements/SecretStorageFooter/SecretStorageFooter.stories.tsx b/clients/web/src/components/elements/SecretStorageFooter/SecretStorageFooter.stories.tsx index 061b1212..6b0ce287 100644 --- a/clients/web/src/components/elements/SecretStorageFooter/SecretStorageFooter.stories.tsx +++ b/clients/web/src/components/elements/SecretStorageFooter/SecretStorageFooter.stories.tsx @@ -22,13 +22,11 @@ type Story = StoryObj; 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 + // ``, 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(); diff --git a/clients/web/src/components/elements/SecretStorageFooter/SecretStorageFooter.test.tsx b/clients/web/src/components/elements/SecretStorageFooter/SecretStorageFooter.test.tsx index 90dca4a2..90548f38 100644 --- a/clients/web/src/components/elements/SecretStorageFooter/SecretStorageFooter.test.tsx +++ b/clients/web/src/components/elements/SecretStorageFooter/SecretStorageFooter.test.tsx @@ -25,25 +25,25 @@ const memory: SecretStorageInfo = { describe("SecretStorageFooter", () => { it("names the keychain with no caveat and no warning tone", () => { renderWithMantine(); - 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 ``, 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(); - 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(); - 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(); - 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", () => { diff --git a/clients/web/src/components/elements/SecretStorageFooter/SecretStorageFooter.tsx b/clients/web/src/components/elements/SecretStorageFooter/SecretStorageFooter.tsx index 8bd445ba..802e530a 100644 --- a/clients/web/src/components/elements/SecretStorageFooter/SecretStorageFooter.tsx +++ b/clients/web/src/components/elements/SecretStorageFooter/SecretStorageFooter.tsx @@ -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 `

`, so a nested one would be a `

` inside a `

` (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. */} - Secrets: {secretStorageLabel(info)} + + Secrets:{" "} + {secretStorageLabel(info)} + {detail && {detail}} ); diff --git a/clients/web/src/components/groups/ClientSettingsModal/ClientSettingsModal.test.tsx b/clients/web/src/components/groups/ClientSettingsModal/ClientSettingsModal.test.tsx index 2e02df15..097165c5 100644 --- a/clients/web/src/components/groups/ClientSettingsModal/ClientSettingsModal.test.tsx +++ b/clients/web/src/components/groups/ClientSettingsModal/ClientSettingsModal.test.tsx @@ -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 ``, 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", () => { diff --git a/clients/web/src/components/groups/ServerSettingsModal/ServerSettingsModal.test.tsx b/clients/web/src/components/groups/ServerSettingsModal/ServerSettingsModal.test.tsx index 5479f22b..ba0303d4 100644 --- a/clients/web/src/components/groups/ServerSettingsModal/ServerSettingsModal.test.tsx +++ b/clients/web/src/components/groups/ServerSettingsModal/ServerSettingsModal.test.tsx @@ -920,9 +920,11 @@ describe("ServerSettingsModal", () => { }} />, ); - expect( - screen.getByText("Secrets: Memory (this session only)"), - ).toBeInTheDocument(); + // Full-text assertion: the "Secrets:" prefix is its own ``, 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", () => {