From 529be76378332a252912d1ba3f7d44770ceb9f07 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Tue, 10 Nov 2026 09:32:00 -0700 Subject: [PATCH] Line up the actions bar's items, and drop debug classes from CSV parts Every action in an ActionsBar is now held to the height and radius of an sm icon button and centred on the row; the Pretty/Raw switch is sized to match instead of sitting shorter than the filter toggle. A CSV part in the multipart viewer loses leftover debug classes that drew it as a small primary-coloured block, and takes the pb-2 the other part viewers use. The harness's multipart fixture gains a CSV part. Co-Authored-By: Claude Opus 5.5 --- apps/yaak-proxy/harness/fixtures.ts | 7 ++++++- packages/ui/src/components/ActionsBar.tsx | 5 ++++- .../http/BodyViewModeSwitch.test.tsx | 19 +++++++++++++++++++ .../components/http/BodyViewModeSwitch.tsx | 9 +++++++-- .../components/viewers/MultipartViewer.tsx | 2 +- 5 files changed, 37 insertions(+), 5 deletions(-) diff --git a/apps/yaak-proxy/harness/fixtures.ts b/apps/yaak-proxy/harness/fixtures.ts index 0bca85476..333b5d310 100644 --- a/apps/yaak-proxy/harness/fixtures.ts +++ b/apps/yaak-proxy/harness/fixtures.ts @@ -92,10 +92,15 @@ const multipartRequest = [ "Content-Type: application/json", "", '{"pages":12,"draft":false}', + `--${multipartBoundary}`, + 'Content-Disposition: form-data; name="rows"; filename="rows.csv"', + "Content-Type: text/csv", + "", + "region,revenue\nnorth,1200\nsouth,980", `--${multipartBoundary}--`, "", ].join("\r\n"); -const textResponse = "Uploaded 2 parts"; +const textResponse = "Uploaded 3 parts"; const htmlResponse = "

Signed in

You can close this window.

"; // A 24x24 PNG gradient diff --git a/packages/ui/src/components/ActionsBar.tsx b/packages/ui/src/components/ActionsBar.tsx index 891d18395..918a5476b 100644 --- a/packages/ui/src/components/ActionsBar.tsx +++ b/packages/ui/src/components/ActionsBar.tsx @@ -14,7 +14,8 @@ interface Props { /** * A row of actions over the bottom-right corner of the content in an `ACTIONS_BAR_CONTAINER`: - * small bordered buttons on the surface colour, so they read over text. Each fades in while the + * small bordered buttons on the surface colour, so they read over text, each held to the height + * and radius of an `sm` icon button and centred on the row. Each fades in while the * container is hovered or the action has focus, unless it carries `PINNED_ACTION`. The row lets * clicks through to the content between its actions. */ @@ -26,6 +27,8 @@ export function ActionsBar({ children, className }: Props) { "absolute bottom-2 left-0 right-0 z-10 flex items-center justify-end gap-1", "pointer-events-none", "*:pointer-events-auto *:bg-surface *:shadow *:transition-opacity *:transform-gpu", + // Every action as tall and as round as a small icon button, so they line up + "*:h-sm *:rounded-md", "[&>:not([data-pinned])]:opacity-0", "group-hover/actions:*:opacity-100! *:focus-within:opacity-100! *:hover:opacity-100!", )} diff --git a/packages/ui/src/components/http/BodyViewModeSwitch.test.tsx b/packages/ui/src/components/http/BodyViewModeSwitch.test.tsx index ffdb05693..2098d4341 100644 --- a/packages/ui/src/components/http/BodyViewModeSwitch.test.tsx +++ b/packages/ui/src/components/http/BodyViewModeSwitch.test.tsx @@ -72,4 +72,23 @@ describe("ActionsBar", () => { expect(markup).toContain('title="Filter"'); expect(markup).toContain("data-pinned"); }); + + test("holds every action to one height and radius, centred on the row", () => { + const markup = renderToStaticMarkup( + + + , + ); + expect(markup).toContain("items-center"); + expect(markup).toContain("*:h-sm"); + expect(markup).toContain("*:rounded-md"); + }); + + test("the switch is sized to the bar's small icon buttons", () => { + const markup = renderToStaticMarkup( + {}} />, + ); + expect(markup).toContain("h-sm!"); + expect(markup).toContain("[&_[role=radio]]:h-full!"); + }); }); diff --git a/packages/ui/src/components/http/BodyViewModeSwitch.tsx b/packages/ui/src/components/http/BodyViewModeSwitch.tsx index e256b33d6..58d47f605 100644 --- a/packages/ui/src/components/http/BodyViewModeSwitch.tsx +++ b/packages/ui/src/components/http/BodyViewModeSwitch.tsx @@ -1,3 +1,4 @@ +import classNames from "classnames"; import { PINNED_ACTION } from "../ActionsBar"; import { SegmentedControl } from "../SegmentedControl"; import type { ResponseViewMode } from "./ResponseBodyViewer"; @@ -65,8 +66,12 @@ export function BodyViewModeSwitch({ pretty, value, onChange }: Props) { size="2xs" value={value} onChange={onChange} - containerClassName="w-auto" - className="p-0.5! gap-0.5! rounded-md! bg-surface! border border-border-subtle" + containerClassName="w-auto h-full" + // As tall as the bar's `sm` icon buttons, its options filling the height inside the border + className={classNames( + "h-sm! m-0! p-0.5! gap-0.5! rounded-md! bg-surface! border border-border-subtle", + "[&_[role=radio]]:h-full!", + )} options={[ { value: "pretty", ...prettyOption }, { value: "raw", ...RAW_OPTION }, diff --git a/packages/ui/src/components/viewers/MultipartViewer.tsx b/packages/ui/src/components/viewers/MultipartViewer.tsx index 0e5b5a42d..8b61a1e5b 100644 --- a/packages/ui/src/components/viewers/MultipartViewer.tsx +++ b/packages/ui/src/components/viewers/MultipartViewer.tsx @@ -275,7 +275,7 @@ function LeafBody({ } if (mediaType?.match(/csv|tab-separated/i)) { - return ; + return ; } if (mediaType?.match(/^text\/html/i) || (!binary && detectedLanguage === "html")) {