feat(web): compact two-line HistoryEntry/NetworkEntry for the narrow column (#1616)

In the pinned monitoring column (embedded), the single-line entry headers wrap
badly. Add compact two-line variants gated on `embedded`:

- HistoryEntry: line 1 = time + client/server badge + elapsed + status; line 2 =
  method (and target, truncated) on the left with the controls on the right,
  where Replay becomes an icon (new ReplayButton element) next to Pin/Expand.
- NetworkEntry: line 1 = time + HTTP method + category + elapsed + status; line 2
  = the URL in a horizontal ScrollArea (new Text `nowrap` variant) with the
  expand toggle on the right.

The full-size (non-embedded) layouts are unchanged. `embedded` is threaded from
the History/Network panels into the entries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
cliffhall 2026-07-08 20:53:23 -04:00
commit 3550b64032
3 changed files with 325 additions and 58 deletions

View file

@ -0,0 +1,19 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { expect, fn, within } from "storybook/test";
import { ReplayButton } from "./ReplayButton";
const meta: Meta<typeof ReplayButton> = {
title: "Elements/ReplayButton",
component: ReplayButton,
args: { onReplay: fn() },
};
export default meta;
type Story = StoryObj<typeof ReplayButton>;
export const Default: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
expect(canvas.getByRole("button", { name: "Replay" })).toBeInTheDocument();
},
};

View file

@ -0,0 +1,19 @@
import { describe, it, expect, vi } from "vitest";
import userEvent from "@testing-library/user-event";
import { renderWithMantine, screen } from "../../../test/renderWithMantine";
import { ReplayButton } from "./ReplayButton";
describe("ReplayButton", () => {
it("renders a labelled replay button", () => {
renderWithMantine(<ReplayButton onReplay={vi.fn()} />);
expect(screen.getByRole("button", { name: "Replay" })).toBeInTheDocument();
});
it("invokes onReplay when clicked", async () => {
const user = userEvent.setup();
const onReplay = vi.fn();
renderWithMantine(<ReplayButton onReplay={onReplay} />);
await user.click(screen.getByRole("button", { name: "Replay" }));
expect(onReplay).toHaveBeenCalledTimes(1);
});
});

View file

@ -0,0 +1,26 @@
import { ActionIcon } from "@mantine/core";
import { MdReplay } from "react-icons/md";
export interface ReplayButtonProps {
/** Re-send the owning history request. */
onReplay: () => void;
}
/**
* Icon form of the "Replay" action, used in the compact (column) HistoryEntry
* layout where the text button is replaced by a replay icon sitting next to the
* pin toggle (#1616). Matches PinToggle's subtle gray icon-button styling.
*/
export function ReplayButton({ onReplay }: ReplayButtonProps) {
return (
<ActionIcon
variant="subtle"
color="gray"
size="md"
aria-label="Replay"
onClick={onReplay}
>
<MdReplay size={18} />
</ActionIcon>
);
}