diff --git a/frontend/src/components/ModeSwitcher.vue b/frontend/src/components/ModeSwitcher.vue index a4324a068..93d27cf6c 100644 --- a/frontend/src/components/ModeSwitcher.vue +++ b/frontend/src/components/ModeSwitcher.vue @@ -17,7 +17,7 @@ class="flex items-center space-x-2 px-3 py-2 bg-base-200 rounded-lg cursor-pointer hover:bg-base-300 transition-colors text-sm" :title="buttonTitle" :aria-expanded="open" - aria-haspopup="true" + aria-haspopup="dialog" @click="toggle" > Mode: @@ -47,8 +47,10 @@
@@ -58,24 +60,26 @@ a Cancel button that cancels nothing is worse than saying nothing. -->
-

Restart pending

-

- /mcp is still serving - {{ activeMeta.label }}. Saved on disk: - {{ pendingMeta.label }} — it applies the next time mcpproxy starts. - Until then {{ pendingMeta.endpoint }} already serves it. -

+ + {{ pendingMeta.label }} after restart — + {{ unmetPrerequisite(pendingMeta) }} + + + {{ pendingMeta.label }} after restart — + {{ pendingMeta.endpoint }} serves it now. +
@@ -83,105 +87,131 @@ half of the decision and must not sit below the fold of a dropdown. Each tab label carries its current value, so both settings are readable before either is opened. --> -
+ +
-
-
-
What /mcp serves
-
What an agent sees the moment it connects
-
+
+
+ What /mcp serves + Docs ↗ +
-
- -
+ + + {{ m.label }} + serving now + after restart + needs restart + + {{ m.summary }} + + {{ unmetPrerequisite(m) }} + + {{ m.endpoint }} + +
-

- {{ ROUTING_RESTART_NOTE }} -

+

+ {{ ROUTING_RESTART_NOTE }} +

-
-
-
How much of each tool is written out
-
- Applies immediately — no restart, and connected clients are told to refetch their tool list. +
+
+ Applies instantly + Docs ↗
-
-
+
All settings - - Docs ↗ -
@@ -230,6 +252,7 @@ import { ref, computed, onBeforeUnmount, watch } from 'vue' import { RouterLink } from 'vue-router' import { useSystemStore } from '@/stores/system' import SerializationAxis from './SerializationAxis.vue' +import type { RoutingModeMeta } from '@/utils/routingMode' import { ROUTING_MODE_LIST, ROUTING_RESTART_NOTE, @@ -240,6 +263,11 @@ import { toolResponseSurface, directToolResponseSurface, TOOL_RESPONSE_CODE_EXEC_NOTE, + TOOL_RESPONSE_CODE_EXEC_HINT, + ROUTING_RESTART_HINT, + ROUTING_MODES_DOC, + TOOL_RESPONSE_DOC, + DIRECT_TOOL_RESPONSE_DOC, } from '@/utils/routingMode' const systemStore = useSystemStore() @@ -267,23 +295,37 @@ const showPendingNotice = computed( * every endpoint's serialization. */ const serializationBadge = computed(() => { + // Code execution PINS its /mcp surface to full schemas (Spec 085 FR-011), so + // a "compact" badge there would advertise a serialization the surface does + // not use. Say nothing rather than something false. + if (systemStore.routingMode === 'code_execution') return '' const isDirect = systemStore.routingMode === 'direct' const value = isDirect ? systemStore.directToolResponseMode : systemStore.toolResponseMode if (!value || value === 'full') return '' - return isDirect ? 'deferred' : 'compact' + // The panel's word for it, not the raw config value: an operator who opens + // the panel to find "compact" will only see "Signatures". + return serializationModeMeta( + isDirect ? DIRECT_TOOL_RESPONSE_MODES : TOOL_RESPONSE_MODES, + value + ).label +}) + +/** The pending notice is one line; the rest of the story is its hover hint. */ +const pendingHint = computed(() => { + const prereq = unmetPrerequisite(pendingMeta.value) + if (prereq) { + return `/mcp is still serving ${activeMeta.value.label}. ${pendingMeta.value.label} is saved and applies the next time mcpproxy starts, but that surface can only call tools once code execution is enabled in Settings.` + } + return `/mcp is still serving ${activeMeta.value.label}. ${pendingMeta.value.label} is saved and applies the next time mcpproxy starts; until then ${pendingMeta.value.endpoint} already serves it.` }) /** - * Both tab chips report what is SERVING, never what is pending: two chips - * disagreeing inside one panel (Surface saying "Direct" while the header badge - * says "Retrieve") reads as a bug. The pending choice is carried by the notice - * at the top of the panel and by the per-option "after restart" badge, which - * say so in words. - * - * The Schema-detail chip names the axis the CURRENT routing mode puts on /mcp — - * the one an operator is asking about when they look at the header. + * The Schema-detail chip names what the CURRENT surface actually sends. Under + * code execution /mcp is pinned to full schemas whatever the config says + * (Spec 085 FR-011), so the chip must not repeat the config there. */ const detailTabValue = computed(() => { + if (systemStore.routingMode === 'code_execution') return 'Full schemas' const isDirect = systemStore.routingMode === 'direct' const value = isDirect ? systemStore.directToolResponseMode : systemStore.toolResponseMode return serializationModeMeta( @@ -297,9 +339,18 @@ const codeExecNote = computed(() => systemStore.routingMode === 'code_execution' ? TOOL_RESPONSE_CODE_EXEC_NOTE : undefined ) +/** + * The prerequisite text for a mode whose prerequisite is NOT met, else "". + * Only code execution has one today, and its flag defaults to off. + */ +function unmetPrerequisite(meta: RoutingModeMeta): string { + if (meta.mode !== 'code_execution' || systemStore.codeExecutionEnabled) return '' + return meta.prerequisiteNote ?? '' +} + const buttonTitle = computed(() => { - const parts = [activeMeta.value.description] - if (systemStore.routingRestartRequired) { + const parts = [`${activeMeta.value.label} mode — ${activeMeta.value.detail}`] + if (showPendingNotice.value) { parts.push(`Restart pending: ${pendingMeta.value.label} applies after mcpproxy restarts.`) } return parts.join(' ') diff --git a/frontend/src/components/SerializationAxis.vue b/frontend/src/components/SerializationAxis.vue index 0394a4735..cbe9c7999 100644 --- a/frontend/src/components/SerializationAxis.vue +++ b/frontend/src/components/SerializationAxis.vue @@ -1,47 +1,70 @@