Phase 2: Refactor Logs cluster to MCP SDK types

Replace string-typed log levels with SDK LoggingLevel across the Logs
group components and LoggingScreen.

- LogControls: type currentLevel, visibleLevels, and all handlers with
  LoggingLevel instead of string; type LOG_LEVELS array and LEVEL_COLORS
  record with LoggingLevel
- LogStreamPanel: type visibleLevels as Record<LoggingLevel, boolean>
  instead of Record<string, boolean>
- LoggingScreen: type currentLevel, onSetLevel, visibleLevels state,
  and toggle handlers with LoggingLevel; extract NO_LEVELS_VISIBLE as
  a module-level constant
- Stories already used LoggingLevel literals; updated type annotations
  to use Record<LoggingLevel, boolean>
- No ConnectedView story changes needed (already passes valid
  LoggingLevel values)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
cliffhall 2026-04-18 19:39:19 -04:00
commit 95e3a2dca7
4 changed files with 31 additions and 20 deletions

View file

@ -1,8 +1,9 @@
import type { Meta, StoryObj } from "@storybook/react-vite"; import type { Meta, StoryObj } from "@storybook/react-vite";
import type { LoggingLevel } from "@modelcontextprotocol/sdk/types.js";
import { fn } from "storybook/test"; import { fn } from "storybook/test";
import { LogControls } from "./LogControls"; import { LogControls } from "./LogControls";
const allLevelsVisible: Record<string, boolean> = { const allLevelsVisible: Record<LoggingLevel, boolean> = {
debug: true, debug: true,
info: true, info: true,
notice: true, notice: true,

View file

@ -8,8 +8,9 @@ import {
Title, Title,
UnstyledButton, UnstyledButton,
} from "@mantine/core"; } from "@mantine/core";
import type { LoggingLevel } from "@modelcontextprotocol/sdk/types.js";
const LOG_LEVELS = [ const LOG_LEVELS: LoggingLevel[] = [
"debug", "debug",
"info", "info",
"notice", "notice",
@ -18,9 +19,9 @@ const LOG_LEVELS = [
"critical", "critical",
"alert", "alert",
"emergency", "emergency",
] as const; ];
const LEVEL_COLORS: Record<string, { c: string }> = { const LEVEL_COLORS: Record<LoggingLevel, { c: string }> = {
debug: { c: "dimmed" }, debug: { c: "dimmed" },
info: { c: "blue" }, info: { c: "blue" },
notice: { c: "teal" }, notice: { c: "teal" },
@ -37,12 +38,12 @@ const SubtleButton = Button.withProps({
}); });
export interface LogControlsProps { export interface LogControlsProps {
currentLevel: string; currentLevel: LoggingLevel;
filterText: string; filterText: string;
visibleLevels: Record<string, boolean>; visibleLevels: Record<LoggingLevel, boolean>;
onSetLevel: (level: string) => void; onSetLevel: (level: LoggingLevel) => void;
onFilterChange: (text: string) => void; onFilterChange: (text: string) => void;
onToggleLevel: (level: string, visible: boolean) => void; onToggleLevel: (level: LoggingLevel, visible: boolean) => void;
onToggleAllLevels: () => void; onToggleAllLevels: () => void;
} }
@ -72,7 +73,7 @@ export function LogControls({
data={LOG_LEVELS.map((level) => ({ value: level, label: level }))} data={LOG_LEVELS.map((level) => ({ value: level, label: level }))}
value={currentLevel} value={currentLevel}
onChange={(value) => { onChange={(value) => {
if (value) onSetLevel(value); if (value) onSetLevel(value as LoggingLevel);
}} }}
/> />
<Button size="sm" onClick={() => onSetLevel(currentLevel)}> <Button size="sm" onClick={() => onSetLevel(currentLevel)}>

View file

@ -9,13 +9,14 @@ import {
Text, Text,
Title, Title,
} from "@mantine/core"; } from "@mantine/core";
import type { LoggingLevel } from "@modelcontextprotocol/sdk/types.js";
import { LogEntry } from "../../elements/LogEntry/LogEntry"; import { LogEntry } from "../../elements/LogEntry/LogEntry";
import type { LogEntryData } from "../../elements/LogEntry/LogEntry"; import type { LogEntryData } from "../../elements/LogEntry/LogEntry";
export interface LogStreamPanelProps { export interface LogStreamPanelProps {
entries: LogEntryData[]; entries: LogEntryData[];
filterText: string; filterText: string;
visibleLevels: Record<string, boolean>; visibleLevels: Record<LoggingLevel, boolean>;
autoScroll: boolean; autoScroll: boolean;
onToggleAutoScroll: () => void; onToggleAutoScroll: () => void;
onCopyAll: () => void; onCopyAll: () => void;
@ -50,7 +51,7 @@ function formatData(data: unknown): string {
function matchesFilters( function matchesFilters(
entry: LogEntryData, entry: LogEntryData,
filterText: string, filterText: string,
visibleLevels: Record<string, boolean>, visibleLevels: Record<LoggingLevel, boolean>,
): boolean { ): boolean {
if (!visibleLevels[entry.params.level]) return false; if (!visibleLevels[entry.params.level]) return false;
if (filterText) { if (filterText) {

View file

@ -1,13 +1,14 @@
import { useState } from "react"; import { useState } from "react";
import { Card, Flex, Stack } from "@mantine/core"; import { Card, Flex, Stack } from "@mantine/core";
import type { LoggingLevel } from "@modelcontextprotocol/sdk/types.js";
import { LogControls } from "../../groups/LogControls/LogControls"; import { LogControls } from "../../groups/LogControls/LogControls";
import { LogStreamPanel } from "../../groups/LogStreamPanel/LogStreamPanel"; import { LogStreamPanel } from "../../groups/LogStreamPanel/LogStreamPanel";
import type { LogEntryData } from "../../elements/LogEntry/LogEntry"; import type { LogEntryData } from "../../elements/LogEntry/LogEntry";
export interface LoggingScreenProps { export interface LoggingScreenProps {
entries: LogEntryData[]; entries: LogEntryData[];
currentLevel: string; currentLevel: LoggingLevel;
onSetLevel: (level: string) => void; onSetLevel: (level: LoggingLevel) => void;
onClear: () => void; onClear: () => void;
onExport: () => void; onExport: () => void;
autoScroll: boolean; autoScroll: boolean;
@ -15,7 +16,7 @@ export interface LoggingScreenProps {
onCopyAll: () => void; onCopyAll: () => void;
} }
const ALL_LEVELS_VISIBLE: Record<string, boolean> = { const ALL_LEVELS_VISIBLE: Record<LoggingLevel, boolean> = {
debug: true, debug: true,
info: true, info: true,
notice: true, notice: true,
@ -26,6 +27,17 @@ const ALL_LEVELS_VISIBLE: Record<string, boolean> = {
emergency: true, emergency: true,
}; };
const NO_LEVELS_VISIBLE: Record<LoggingLevel, boolean> = {
debug: false,
info: false,
notice: false,
warning: false,
error: false,
critical: false,
alert: false,
emergency: false,
};
const ScreenLayout = Flex.withProps({ const ScreenLayout = Flex.withProps({
variant: "screen", variant: "screen",
h: "calc(100vh - var(--app-shell-header-height, 0px))", h: "calc(100vh - var(--app-shell-header-height, 0px))",
@ -55,16 +67,12 @@ export function LoggingScreen({
}: LoggingScreenProps) { }: LoggingScreenProps) {
const [filterText, setFilterText] = useState(""); const [filterText, setFilterText] = useState("");
const [visibleLevels, setVisibleLevels] = const [visibleLevels, setVisibleLevels] =
useState<Record<string, boolean>>(ALL_LEVELS_VISIBLE); useState<Record<LoggingLevel, boolean>>(ALL_LEVELS_VISIBLE);
function handleToggleLevel(level: string, visible: boolean) { function handleToggleLevel(level: LoggingLevel, visible: boolean) {
setVisibleLevels((prev) => ({ ...prev, [level]: visible })); setVisibleLevels((prev) => ({ ...prev, [level]: visible }));
} }
const NO_LEVELS_VISIBLE: Record<string, boolean> = Object.fromEntries(
Object.keys(ALL_LEVELS_VISIBLE).map((k) => [k, false]),
);
function handleToggleAllLevels() { function handleToggleAllLevels() {
const allSelected = Object.values(visibleLevels).every(Boolean); const allSelected = Object.values(visibleLevels).every(Boolean);
setVisibleLevels(allSelected ? NO_LEVELS_VISIBLE : ALL_LEVELS_VISIBLE); setVisibleLevels(allSelected ? NO_LEVELS_VISIBLE : ALL_LEVELS_VISIBLE);