diff --git a/docs/features/editor.mdx b/docs/features/editor.mdx
index 79566715..afa0cfc8 100644
--- a/docs/features/editor.mdx
+++ b/docs/features/editor.mdx
@@ -100,7 +100,7 @@ The editor card has three tabs:
| Tab | Purpose |
|-----|---------|
| `compose.yaml` | YAML editor with syntax highlighting. Always available. |
-| `.env` | Plaintext editor for the discovered env file. Disabled when the stack has no env file. |
+| `.env` | Environment file editor with syntax highlighting. Disabled when the stack has no env file. |
| **Files** | Browseable directory tree for the stack folder. See the dedicated [Stack File Explorer](/features/stack-file-explorer) page for permissions, upload caps, and protected-file behavior. |
Above the editor area, the toolbar carries:
diff --git a/frontend/src/components/EditorLayout/EditorView.tsx b/frontend/src/components/EditorLayout/EditorView.tsx
index 45e08ea8..c18a4439 100644
--- a/frontend/src/components/EditorLayout/EditorView.tsx
+++ b/frontend/src/components/EditorLayout/EditorView.tsx
@@ -524,7 +524,7 @@ export function EditorView(props: EditorViewProps) {
}>
{ monacoEditorRef.current = editor; }}
diff --git a/frontend/src/components/EditorLayout/__tests__/EditorView.test.tsx b/frontend/src/components/EditorLayout/__tests__/EditorView.test.tsx
new file mode 100644
index 00000000..c1e34141
--- /dev/null
+++ b/frontend/src/components/EditorLayout/__tests__/EditorView.test.tsx
@@ -0,0 +1,139 @@
+import { describe, it, expect, vi, afterEach } from 'vitest';
+import { render } from '@testing-library/react';
+import type { ReactNode } from 'react';
+import { EditorView } from '../EditorView';
+import type { EditorViewProps } from '../EditorView';
+
+// Capture Monaco language/value props to assert env tab gets 'ini', compose gets 'yaml'.
+let lastLanguage: string | undefined;
+let lastValue: string | undefined;
+vi.mock('@/lib/monacoLoader', () => ({
+ Editor: ({ language, value }: { language?: string; value?: string }) => {
+ lastLanguage = language;
+ lastValue = value;
+ return ;
+ },
+}));
+
+// Stub heavy children; this test only asserts the Monaco language prop.
+vi.mock('../editor-view-blocks', () => ({
+ StackIdentityHeader: () => identity-header
,
+ ContainersHealth: () => health-pane
,
+ StackLogsSection: () => logs-pane
,
+}));
+vi.mock('../../StackAnatomyPanel', () => ({
+ default: () => anatomy-pane
,
+}));
+vi.mock('../StackOperationBanner', () => ({ StackOperationBanner: () => null }));
+vi.mock('../../ErrorBoundary', () => ({ default: ({ children }: { children: ReactNode }) => <>{children}> }));
+vi.mock('@/hooks/use-is-mobile', () => ({ useIsMobile: () => false }));
+
+function makeProps(over: Partial = {}): EditorViewProps {
+ return {
+ stackName: 'web',
+ isDarkMode: false,
+ activeNode: null,
+ containers: [],
+ containerStats: {},
+ containerStatsError: null,
+ content: '',
+ envContent: '',
+ envExists: false,
+ envFiles: [],
+ selectedEnvFile: '',
+ isFileLoading: false,
+ backupInfo: { exists: false, timestamp: null },
+ gitSourcePendingMap: {},
+ notifications: [],
+ copiedDigest: null,
+ loadingAction: null,
+ stackMisconfigScanning: false,
+ activeTab: 'compose',
+ isEditing: false,
+ editingCompose: false,
+ logsMode: 'structured',
+ can: () => true,
+ isAdmin: false,
+ trivy: { available: false },
+ copiedDigestTimerRef: { current: null },
+ deployStack: vi.fn(),
+ restartStack: vi.fn(),
+ stopStack: vi.fn(),
+ updateStack: vi.fn(),
+ rollbackStack: vi.fn(),
+ scanStackConfig: vi.fn(),
+ enterEditMode: vi.fn(),
+ requestSave: vi.fn(),
+ requestSaveAndDeploy: vi.fn(),
+ discardChanges: vi.fn(),
+ setContent: vi.fn(),
+ setEnvContent: vi.fn(),
+ changeEnvFile: vi.fn(),
+ openLogViewer: vi.fn(),
+ openBashModal: vi.fn(),
+ serviceAction: vi.fn(),
+ setActiveTab: vi.fn(),
+ setLogsMode: vi.fn(),
+ setEditingCompose: vi.fn(),
+ setGitSourceOpen: vi.fn(),
+ setCopiedDigest: vi.fn(),
+ requestDeleteStack: vi.fn(),
+ onRefreshState: vi.fn(),
+ onDismissRecovery: vi.fn(),
+ panelStartedAt: null,
+ onMobileBack: vi.fn(),
+ onCloseEditor: vi.fn(),
+ hasUnsavedChanges: () => false,
+ ...over,
+ };
+}
+
+describe('EditorView Monaco language prop', () => {
+ afterEach(() => {
+ lastLanguage = undefined;
+ lastValue = undefined;
+ });
+
+ it('passes language="ini" when the env tab is active', () => {
+ render(
+ ,
+ );
+ expect(lastLanguage).toBe('ini');
+ expect(lastValue).toBe('KEY=val\n# a comment');
+ });
+
+ it('passes language="yaml" when the compose tab is active', () => {
+ render(
+ ,
+ );
+ expect(lastLanguage).toBe('yaml');
+ expect(lastValue).toBe('services:\n web:\n image: nginx');
+ });
+
+ it('does not mount Monaco when the files tab is active', () => {
+ render(
+ ,
+ );
+ // StackFileExplorer replaces Monaco in the files tab path.
+ expect(lastLanguage).toBeUndefined();
+ expect(lastValue).toBeUndefined();
+ });
+});