Skip to content

Commit febafda

Browse files
committed
feat(servers): Phase 1 — Manifest modal: fullscreen sizing + search fixes
Autonomous decisions: - Disabled search button when editorLoadFailed — search requires Monaco; textarea fallback has no find widget - Placed search toolbar button after Format — matches planning doc snippet order (Format then Search) Signed-off-by: crimsonsunset <jsangio1@gmail.com>
1 parent 9c478d2 commit febafda

2 files changed

Lines changed: 38 additions & 11 deletions

File tree

apps/desktop/src/components/ConfigEditorModal.tsx

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useState, useEffect, useCallback, useRef } from 'react';
22
import { useTranslation } from 'react-i18next';
3-
import { X, Save, Loader2, AlertTriangle, Wand2, Plus } from 'lucide-react';
3+
import { X, Save, Loader2, AlertTriangle, Wand2, Plus, Search } from 'lucide-react';
44
import { readSpaceConfig, saveSpaceConfig } from '@/lib/api/spaces';
55
import { type Monaco } from '@monaco-editor/react';
66
import type { editor } from 'monaco-editor';
@@ -171,6 +171,20 @@ export function ConfigEditorModal({
171171
}
172172
}, [content, t]);
173173

174+
/**
175+
* Whether Monaco's find widget is currently visible in the editor DOM.
176+
*/
177+
const isFindWidgetOpen = useCallback((): boolean => {
178+
return !!editorRef.current?.getDomNode()?.querySelector('.find-widget.visible');
179+
}, []);
180+
181+
/**
182+
* Open Monaco's built-in find widget for the JSON editor.
183+
*/
184+
const handleSearch = useCallback(() => {
185+
editorRef.current?.getAction('actions.find')?.run();
186+
}, []);
187+
174188
const handleInsertCustomServer = useCallback(() => {
175189
try {
176190
const parsed = JSON.parse(content || '{"mcpServers":{}}') as SpaceConfigJson;
@@ -260,24 +274,25 @@ export function ConfigEditorModal({
260274
// Keyboard shortcuts
261275
useEffect(() => {
262276
const handleKeyDown = (e: KeyboardEvent) => {
263-
// Ctrl+Shift+F to format
264-
if (e.ctrlKey && e.shiftKey && e.key === 'F') {
277+
const mod = e.ctrlKey || e.metaKey;
278+
279+
if (mod && e.shiftKey && e.key === 'F') {
265280
e.preventDefault();
266281
handleFormat();
267282
}
268-
// Ctrl+S to save
269-
if (e.ctrlKey && e.key === 's') {
283+
284+
if (mod && e.key === 's') {
270285
e.preventDefault();
271286
handleSave();
272287
}
273-
// Escape to close
274-
if (e.key === 'Escape') {
288+
289+
if (e.key === 'Escape' && !isFindWidgetOpen()) {
275290
onClose();
276291
}
277292
};
278293
window.addEventListener('keydown', handleKeyDown);
279294
return () => window.removeEventListener('keydown', handleKeyDown);
280-
}, [handleFormat, handleSave, onClose]);
295+
}, [handleFormat, handleSave, isFindWidgetOpen, onClose]);
281296

282297
return (
283298
<>
@@ -286,11 +301,11 @@ export function ConfigEditorModal({
286301
onClose={(id) => toasts.find((toast) => toast.id === id)?.onClose(id)}
287302
/>
288303
<div
289-
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4 backdrop-blur-sm"
304+
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-6 backdrop-blur-sm"
290305
data-testid="config-editor-modal-overlay"
291306
>
292307
<div
293-
className="flex h-[80vh] w-full max-w-4xl flex-col rounded-xl border border-[rgb(var(--border))] bg-[rgb(var(--surface))] shadow-2xl"
308+
className="flex h-[95vh] w-[95vw] flex-col rounded-xl border border-[rgb(var(--border))] bg-[rgb(var(--surface))] shadow-2xl"
294309
data-testid="config-editor-modal"
295310
>
296311
{/* Header */}
@@ -341,6 +356,16 @@ export function ConfigEditorModal({
341356
{t('configEditorModal.format')}
342357
</button>
343358

359+
<button
360+
onClick={handleSearch}
361+
disabled={isLoading || editorLoadFailed}
362+
className="flex items-center gap-2 rounded-lg px-3 py-1.5 text-sm font-medium text-[rgb(var(--muted))] transition-colors hover:bg-[rgb(var(--surface-hover))] hover:text-[rgb(var(--foreground))] disabled:opacity-50"
363+
title={t('configEditorModal.searchTitle')}
364+
>
365+
<Search className="h-4 w-4" />
366+
{t('configEditorModal.search')}
367+
</button>
368+
344369
<button
345370
onClick={handleInsertCustomServer}
346371
disabled={isLoading || !isValidJson}

apps/desktop/src/locales/en/servers.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,11 +246,13 @@
246246
"save": "Save",
247247
"format": "Format",
248248
"formatTitle": "Format JSON (Ctrl+Shift+F)",
249+
"search": "Search",
250+
"searchTitle": "Find in JSON (Ctrl+F / Cmd+F)",
249251
"insertServer": "Insert Server",
250252
"insertServerTitle": "Insert another unique custom server entry",
251253
"schemaError": "Schema Error",
252254
"invalidJson": "Invalid JSON",
253-
"keyboardHints": "Ctrl+S save · Ctrl+Shift+F format",
255+
"keyboardHints": "Ctrl/Cmd+S save · Ctrl/Cmd+Shift+F format · Ctrl/Cmd+F search",
254256
"editorLoadFailed": "Editor failed to load. You can still edit JSON below, or restart the app.",
255257
"moreErrors": "(+{{count}} more)",
256258
"toast": {

0 commit comments

Comments
 (0)