Merge remote-tracking branch 'origin/v2/main' into 1579-wave-1
# Conflicts: # clients/cli/package-lock.json # clients/cli/package.json # clients/web/src/App.tsx # clients/web/src/lib/environmentFactory.test.ts # clients/web/src/lib/environmentFactory.ts # clients/web/src/lib/remoteOAuthStorage.test.ts # clients/web/src/lib/remoteOAuthStorage.ts # clients/web/src/test/core/auth/connection-state.test.ts # clients/web/src/test/core/auth/providers.test.ts # clients/web/src/test/core/auth/storage-browser.test.ts # clients/web/src/test/core/auth/storage-remote.test.ts # clients/web/src/test/core/mcp/oauthManager.test.ts # clients/web/src/test/integration/auth/node/storage.test.ts # clients/web/src/test/integration/storage/adapters.test.ts # clients/web/src/utils/clearServerOAuthState.test.ts # clients/web/src/utils/clearServerOAuthState.ts # core/auth/connection-state.ts # core/auth/oauth-storage.ts # core/auth/providers.ts # core/auth/storage.ts # core/auth/store.ts # core/storage/adapters/remote-storage.ts
This commit is contained in:
commit
32a3610737
6 changed files with 5046 additions and 3271 deletions
|
|
@ -111,4 +111,48 @@ describe("HistoryScreen", () => {
|
|||
expect.objectContaining({ methodFilter: undefined }),
|
||||
);
|
||||
});
|
||||
|
||||
it("renders a pin-as-column button when onPin is provided and invokes it", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onPin = vi.fn();
|
||||
renderWithMantine(<HistoryScreen {...baseProps} onPin={onPin} />);
|
||||
await user.click(screen.getByRole("button", { name: "Pin as column" }));
|
||||
expect(onPin).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("drops the filter sidebar when embedded, keeping the request list", () => {
|
||||
renderWithMantine(<HistoryScreen {...baseProps} embedded />);
|
||||
expect(screen.getByText("Requests")).toBeInTheDocument();
|
||||
// The sidebar (HistoryControls, with its Search box) is not rendered.
|
||||
expect(screen.queryByPlaceholderText("Search...")).toBeNull();
|
||||
});
|
||||
|
||||
it("applies the search text but ignores the method filter when embedded", () => {
|
||||
renderWithMantine(
|
||||
<HistoryScreen
|
||||
{...baseProps}
|
||||
ui={{
|
||||
...EMPTY_HISTORY_UI,
|
||||
// Method filter would exclude the tools/list entries on the full-size
|
||||
// screen...
|
||||
methodFilter: "resources/list",
|
||||
// ...but the column search matches them.
|
||||
search: "tools",
|
||||
}}
|
||||
embedded
|
||||
/>,
|
||||
);
|
||||
expect(screen.queryByText("No request history")).toBeNull();
|
||||
});
|
||||
|
||||
it("hides entries not matching the column search when embedded", () => {
|
||||
renderWithMantine(
|
||||
<HistoryScreen
|
||||
{...baseProps}
|
||||
ui={{ ...EMPTY_HISTORY_UI, search: "zzz-no-match" }}
|
||||
embedded
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText("No request history")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ export interface HistoryScreenProps {
|
|||
onSortChange: (next: SortDirection) => void;
|
||||
compact: boolean;
|
||||
onToggleCompact: () => void;
|
||||
/** See LoggingScreen: shows a "pin as column" button when set (#1616). */
|
||||
onPin?: () => void;
|
||||
/** See LoggingScreen: fills the parent height and drops the filter sidebar. */
|
||||
embedded?: boolean;
|
||||
}
|
||||
|
||||
// Search text, method filter, and per-direction visibility — controlled by the
|
||||
|
|
@ -69,6 +73,8 @@ export function HistoryScreen({
|
|||
onSortChange,
|
||||
compact,
|
||||
onToggleCompact,
|
||||
onPin,
|
||||
embedded = false,
|
||||
}: HistoryScreenProps) {
|
||||
const { search, methodFilter, visibleDirections } = ui;
|
||||
|
||||
|
|
@ -101,23 +107,25 @@ export function HistoryScreen({
|
|||
}, [ui, visibleDirections, onUiChange]);
|
||||
|
||||
return (
|
||||
<ScreenLayout>
|
||||
<Sidebar>
|
||||
<SidebarCard>
|
||||
<HistoryControls
|
||||
searchText={search}
|
||||
methodFilter={methodFilter}
|
||||
availableMethods={availableMethods}
|
||||
visibleDirections={visibleDirections}
|
||||
onSearchChange={(value) => onUiChange({ ...ui, search: value })}
|
||||
onMethodFilterChange={(value) =>
|
||||
onUiChange({ ...ui, methodFilter: value })
|
||||
}
|
||||
onToggleDirection={handleToggleDirection}
|
||||
onToggleAllDirections={handleToggleAllDirections}
|
||||
/>
|
||||
</SidebarCard>
|
||||
</Sidebar>
|
||||
<ScreenLayout h={embedded ? "100%" : undefined}>
|
||||
{embedded ? null : (
|
||||
<Sidebar>
|
||||
<SidebarCard>
|
||||
<HistoryControls
|
||||
searchText={search}
|
||||
methodFilter={methodFilter}
|
||||
availableMethods={availableMethods}
|
||||
visibleDirections={visibleDirections}
|
||||
onSearchChange={(value) => onUiChange({ ...ui, search: value })}
|
||||
onMethodFilterChange={(value) =>
|
||||
onUiChange({ ...ui, methodFilter: value })
|
||||
}
|
||||
onToggleDirection={handleToggleDirection}
|
||||
onToggleAllDirections={handleToggleAllDirections}
|
||||
/>
|
||||
</SidebarCard>
|
||||
</Sidebar>
|
||||
)}
|
||||
<HistoryListPanel
|
||||
entries={entries}
|
||||
pinnedIds={pinnedIds}
|
||||
|
|
@ -134,6 +142,8 @@ export function HistoryScreen({
|
|||
onSortChange={onSortChange}
|
||||
compact={compact}
|
||||
onToggleCompact={onToggleCompact}
|
||||
onPin={onPin}
|
||||
embedded={embedded}
|
||||
/>
|
||||
</ScreenLayout>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -131,4 +131,62 @@ describe("LoggingScreen", () => {
|
|||
await user.click(screen.getByRole("button", { name: "Set" }));
|
||||
expect(onSetLevel).toHaveBeenCalledWith("info");
|
||||
});
|
||||
|
||||
it("renders a pin-as-column button when onPin is provided and invokes it", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onPin = vi.fn();
|
||||
renderWithMantine(<LoggingScreen {...baseProps} onPin={onPin} />);
|
||||
await user.click(screen.getByRole("button", { name: "Pin as column" }));
|
||||
expect(onPin).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("drops the filter sidebar when embedded, keeping the stream", () => {
|
||||
renderWithMantine(<LoggingScreen {...baseProps} embedded />);
|
||||
expect(screen.getByText("Log Stream")).toBeInTheDocument();
|
||||
// The sidebar (LogControls, with its Set button) is not rendered.
|
||||
expect(screen.queryByRole("button", { name: "Set" })).toBeNull();
|
||||
});
|
||||
|
||||
it("applies the search text but ignores the level filter when embedded", () => {
|
||||
const entries = [
|
||||
{
|
||||
receivedAt: new Date(),
|
||||
params: { level: "info" as const, data: "hello" },
|
||||
},
|
||||
];
|
||||
renderWithMantine(
|
||||
<LoggingScreen
|
||||
{...baseProps}
|
||||
entries={entries}
|
||||
ui={{
|
||||
...EMPTY_LOGS_UI,
|
||||
// The level filter would hide this entry on the full-size screen...
|
||||
visibleLevels: { ...EMPTY_LOGS_UI.visibleLevels, info: false },
|
||||
// ...but the column search matches it.
|
||||
filterText: "hello",
|
||||
}}
|
||||
embedded
|
||||
/>,
|
||||
);
|
||||
// Search matches + level filter is ignored → the entry shows.
|
||||
expect(screen.getByText("hello")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("hides entries not matching the column search when embedded", () => {
|
||||
const entries = [
|
||||
{
|
||||
receivedAt: new Date(),
|
||||
params: { level: "info" as const, data: "hello" },
|
||||
},
|
||||
];
|
||||
renderWithMantine(
|
||||
<LoggingScreen
|
||||
{...baseProps}
|
||||
entries={entries}
|
||||
ui={{ ...EMPTY_LOGS_UI, filterText: "zzz-no-match" }}
|
||||
embedded
|
||||
/>,
|
||||
);
|
||||
expect(screen.queryByText("hello")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,6 +16,17 @@ export interface LoggingScreenProps {
|
|||
onExport: () => void;
|
||||
sortDirection: SortDirection;
|
||||
onSortChange: (next: SortDirection) => void;
|
||||
/**
|
||||
* When set, the stream panel shows a "pin as column" button (#1616). Omitted
|
||||
* when already embedded, or when pinning isn't currently available.
|
||||
*/
|
||||
onPin?: () => void;
|
||||
/**
|
||||
* True when rendered inside the monitoring column: the screen fills its
|
||||
* parent's height (instead of the viewport calc) and drops the filter
|
||||
* sidebar so the narrow column is stream-only.
|
||||
*/
|
||||
embedded?: boolean;
|
||||
}
|
||||
|
||||
// Filter text + visible-level set — controlled by the parent (App) as one
|
||||
|
|
@ -52,6 +63,8 @@ export function LoggingScreen({
|
|||
onExport,
|
||||
sortDirection,
|
||||
onSortChange,
|
||||
onPin,
|
||||
embedded = false,
|
||||
}: LoggingScreenProps) {
|
||||
const { filterText, visibleLevels } = ui;
|
||||
|
||||
|
|
@ -71,20 +84,24 @@ export function LoggingScreen({
|
|||
}
|
||||
|
||||
return (
|
||||
<ScreenLayout>
|
||||
<Sidebar>
|
||||
<SidebarCard>
|
||||
<LogControls
|
||||
currentLevel={currentLevel}
|
||||
filterText={filterText}
|
||||
visibleLevels={visibleLevels}
|
||||
onSetLevel={onSetLevel}
|
||||
onFilterChange={(value) => onUiChange({ ...ui, filterText: value })}
|
||||
onToggleLevel={handleToggleLevel}
|
||||
onToggleAllLevels={handleToggleAllLevels}
|
||||
/>
|
||||
</SidebarCard>
|
||||
</Sidebar>
|
||||
<ScreenLayout h={embedded ? "100%" : undefined}>
|
||||
{embedded ? null : (
|
||||
<Sidebar>
|
||||
<SidebarCard>
|
||||
<LogControls
|
||||
currentLevel={currentLevel}
|
||||
filterText={filterText}
|
||||
visibleLevels={visibleLevels}
|
||||
onSetLevel={onSetLevel}
|
||||
onFilterChange={(value) =>
|
||||
onUiChange({ ...ui, filterText: value })
|
||||
}
|
||||
onToggleLevel={handleToggleLevel}
|
||||
onToggleAllLevels={handleToggleAllLevels}
|
||||
/>
|
||||
</SidebarCard>
|
||||
</Sidebar>
|
||||
)}
|
||||
<LogStreamPanel
|
||||
entries={entries}
|
||||
filterText={filterText}
|
||||
|
|
@ -93,6 +110,8 @@ export function LoggingScreen({
|
|||
onExport={onExport}
|
||||
sortDirection={sortDirection}
|
||||
onSortChange={onSortChange}
|
||||
onPin={onPin}
|
||||
embedded={embedded}
|
||||
/>
|
||||
</ScreenLayout>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -163,4 +163,49 @@ describe("NetworkScreen", () => {
|
|||
expect(screen.getByRole("button", { name: "Clear" })).toBeDisabled();
|
||||
expect(screen.getByRole("button", { name: "Export" })).toBeDisabled();
|
||||
});
|
||||
|
||||
it("renders a pin-as-column button when onPin is provided and invokes it", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onPin = vi.fn();
|
||||
renderWithMantine(<NetworkScreen {...baseProps} onPin={onPin} />);
|
||||
await user.click(screen.getByRole("button", { name: "Pin as column" }));
|
||||
expect(onPin).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("drops the filter sidebar when embedded, keeping the request list", () => {
|
||||
renderWithMantine(<NetworkScreen {...baseProps} embedded />);
|
||||
// The sidebar (NetworkControls, with its Search box) is not rendered.
|
||||
expect(screen.queryByPlaceholderText("Search...")).toBeNull();
|
||||
// The stream toolbar (Export) is still present.
|
||||
expect(screen.getByRole("button", { name: "Export" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("applies the search text but ignores the category filter when embedded", () => {
|
||||
renderWithMantine(
|
||||
<NetworkScreen
|
||||
{...baseProps}
|
||||
ui={{
|
||||
...EMPTY_NETWORK_UI,
|
||||
// Category filter would hide the transport entry on the full-size
|
||||
// screen...
|
||||
visibleCategories: { auth: true, transport: false },
|
||||
// ...but the column search matches it.
|
||||
filterText: "example.com",
|
||||
}}
|
||||
embedded
|
||||
/>,
|
||||
);
|
||||
expect(screen.queryByText("No network requests")).toBeNull();
|
||||
});
|
||||
|
||||
it("hides entries not matching the column search when embedded", () => {
|
||||
renderWithMantine(
|
||||
<NetworkScreen
|
||||
{...baseProps}
|
||||
ui={{ ...EMPTY_NETWORK_UI, filterText: "zzz-no-match" }}
|
||||
embedded
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText("No network requests")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ export interface NetworkScreenProps {
|
|||
onSortChange: (next: SortDirection) => void;
|
||||
compact: boolean;
|
||||
onToggleCompact: () => void;
|
||||
/** See LoggingScreen: shows a "pin as column" button when set (#1616). */
|
||||
onPin?: () => void;
|
||||
/** See LoggingScreen: fills the parent height and drops the filter sidebar. */
|
||||
embedded?: boolean;
|
||||
}
|
||||
|
||||
// Filter text + visible-category set — controlled by the parent (App) as one
|
||||
|
|
@ -57,6 +61,8 @@ export function NetworkScreen({
|
|||
onSortChange,
|
||||
compact,
|
||||
onToggleCompact,
|
||||
onPin,
|
||||
embedded = false,
|
||||
}: NetworkScreenProps) {
|
||||
const { filterText, visibleCategories } = ui;
|
||||
|
||||
|
|
@ -81,18 +87,22 @@ export function NetworkScreen({
|
|||
}
|
||||
|
||||
return (
|
||||
<ScreenLayout>
|
||||
<Sidebar>
|
||||
<SidebarCard>
|
||||
<NetworkControls
|
||||
filterText={filterText}
|
||||
visibleCategories={visibleCategories}
|
||||
onFilterChange={(value) => onUiChange({ ...ui, filterText: value })}
|
||||
onToggleCategory={handleToggleCategory}
|
||||
onToggleAllCategories={handleToggleAllCategories}
|
||||
/>
|
||||
</SidebarCard>
|
||||
</Sidebar>
|
||||
<ScreenLayout h={embedded ? "100%" : undefined}>
|
||||
{embedded ? null : (
|
||||
<Sidebar>
|
||||
<SidebarCard>
|
||||
<NetworkControls
|
||||
filterText={filterText}
|
||||
visibleCategories={visibleCategories}
|
||||
onFilterChange={(value) =>
|
||||
onUiChange({ ...ui, filterText: value })
|
||||
}
|
||||
onToggleCategory={handleToggleCategory}
|
||||
onToggleAllCategories={handleToggleAllCategories}
|
||||
/>
|
||||
</SidebarCard>
|
||||
</Sidebar>
|
||||
)}
|
||||
<NetworkStreamPanel
|
||||
entries={entries}
|
||||
filterText={filterText}
|
||||
|
|
@ -103,6 +113,8 @@ export function NetworkScreen({
|
|||
onSortChange={onSortChange}
|
||||
compact={compact}
|
||||
onToggleCompact={onToggleCompact}
|
||||
onPin={onPin}
|
||||
embedded={embedded}
|
||||
/>
|
||||
</ScreenLayout>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue