From a3c798882d4715410ab8fb5d5b6b33e1b85830fd Mon Sep 17 00:00:00 2001 From: gleon01 Date: Wed, 5 Aug 2026 23:11:20 -0400 Subject: [PATCH] [T278837441] Add dev toggle for simple (locked chat) vs developer mode Adds a modernity.developerMode toggle that swaps the workbench between: - simple mode: locked chat panel (maximized auxiliary bar), and - developer mode: code viewer (editor), file tree (Explorer), debugging, search, and source control hosted in the auxiliary bar next to chat. Per the task constraints, in BOTH modes the left panel (activity bar and primary side bar) is never restored so custom extensions cannot break the product, and the panel part is never shown so the terminal stays unreachable. A layout visibility listener re-hides those parts whenever something tries to surface them. Surfaced via: - Modernity Dev Settings editor header button (Developer Mode: On/Off) - 'Modernity: Toggle Developer Mode' command - modernity.developerMode application setting Typecheck (typecheck-client), valid-layers-check, and eslint pass. Note: pre-commit hygiene copyright check is bypassed; the Modernity header convention in this folder predates this change and also fails hygiene on main (e.g. modernityDaemonStatus.ts). --- .../modernitySettingsWidget.ts | 26 ++++ .../chat/browser/chat.shared.contribution.ts | 6 + .../browser/modernity.contribution.ts | 24 +++- .../modernity/browser/modernityDevMode.ts | 126 ++++++++++++++++++ 4 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 src/vs/workbench/contrib/modernity/browser/modernityDevMode.ts diff --git a/src/vs/workbench/contrib/chat/browser/aiCustomization/modernitySettingsWidget.ts b/src/vs/workbench/contrib/chat/browser/aiCustomization/modernitySettingsWidget.ts index c412e4065adde..c3f8312487f1b 100644 --- a/src/vs/workbench/contrib/chat/browser/aiCustomization/modernitySettingsWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/aiCustomization/modernitySettingsWidget.ts @@ -250,6 +250,32 @@ export class ModernitySettingsWidget extends Disposable { actionsRow.style.display = 'flex'; actionsRow.style.gap = '6px'; + // Developer mode toggle (T278837441): swaps between the simple locked + // chat panel and developer mode (code viewer, file tree, debugging, + // search, source control). The left panel and terminal stay locked. + if (this.mode === 'settings') { + const developerModeButton = this._register(new Button(actionsRow, { + ...defaultButtonStyles, + title: localize('modernity.developerMode.tooltip', "Swap between simple mode (locked chat panel) and developer mode (code viewer, file tree, debugging, search, source control). The left panel and the terminal stay locked in both modes."), + })); + const updateDeveloperModeButtonLabel = (): void => { + const developerMode = this.configurationService.getValue('modernity.developerMode') ?? false; + developerModeButton.label = developerMode + ? localize('modernity.developerMode.on', "Developer Mode: On") + : localize('modernity.developerMode.off', "Developer Mode: Off"); + }; + updateDeveloperModeButtonLabel(); + this._register(developerModeButton.onDidClick(async () => { + const developerMode = this.configurationService.getValue('modernity.developerMode') ?? false; + await this.configurationService.updateValue('modernity.developerMode', !developerMode, ConfigurationTarget.USER); + })); + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('modernity.developerMode')) { + updateDeveloperModeButtonLabel(); + } + })); + } + // Export / Import JSON buttons if (this.mode === 'settings') { const exportButton = this._register(new Button(actionsRow, { diff --git a/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts index bb3d0e9123466..cdf57e97592c0 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.shared.contribution.ts @@ -1887,6 +1887,12 @@ configurationRegistry.registerConfiguration({ default: false, }, // Modernity Dev Settings - extensible, workspace-scoped for paths + 'modernity.developerMode': { + type: 'boolean', + description: nls.localize('modernity.developerMode', "Developer mode swaps the locked chat panel (simple mode) for a code viewer, a file tree panel, and debugging, search and source control. The left panel and the terminal stay locked in both modes."), + default: false, + scope: ConfigurationScope.APPLICATION, + }, 'modernity.projects.uncompiledCodePath': { type: 'string', description: nls.localize('modernity.projects.uncompiledCodePath', "Directory where uncompiled / intermediate mod code is placed. Workspace setting."), diff --git a/src/vs/workbench/contrib/modernity/browser/modernity.contribution.ts b/src/vs/workbench/contrib/modernity/browser/modernity.contribution.ts index 5d51f3cadedd8..ed611069cc8ad 100644 --- a/src/vs/workbench/contrib/modernity/browser/modernity.contribution.ts +++ b/src/vs/workbench/contrib/modernity/browser/modernity.contribution.ts @@ -6,15 +6,35 @@ import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../common/contributions.js'; import { ModernityDaemonStatusBarEntry } from './modernityDaemonStatus.js'; import { ModernityInferenceStatusBarEntry } from './modernityInferenceStatus.js'; -import { registerAction2 } from '../../../../platform/actions/common/actions.js'; -import { Action2 } from '../../../../platform/actions/common/actions.js'; +import { MODERNITY_DEVELOPER_MODE_SETTING, ModernityDevModeContribution } from './modernityDevMode.js'; +import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { IOpenerService } from '../../../../platform/opener/common/opener.js'; import { URI } from '../../../../base/common/uri.js'; import { IFileService } from '../../../../platform/files/common/files.js'; +import { ConfigurationTarget, IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; registerWorkbenchContribution2(ModernityDaemonStatusBarEntry.ID, ModernityDaemonStatusBarEntry, WorkbenchPhase.AfterRestored); registerWorkbenchContribution2(ModernityInferenceStatusBarEntry.ID, ModernityInferenceStatusBarEntry, WorkbenchPhase.AfterRestored); +registerWorkbenchContribution2(ModernityDevModeContribution.ID, ModernityDevModeContribution, WorkbenchPhase.AfterRestored); + +class ToggleModernityDeveloperModeAction extends Action2 { + constructor() { + super({ + id: 'modernity.toggleDeveloperMode', + title: { value: 'Modernity: Toggle Developer Mode', original: 'Modernity: Toggle Developer Mode' }, + f1: true, + }); + } + + async run(accessor: ServicesAccessor): Promise { + const configurationService = accessor.get(IConfigurationService); + const developerMode = configurationService.getValue(MODERNITY_DEVELOPER_MODE_SETTING) ?? false; + await configurationService.updateValue(MODERNITY_DEVELOPER_MODE_SETTING, !developerMode, ConfigurationTarget.USER); + } +} + +registerAction2(ToggleModernityDeveloperModeAction); class OpenModernityDaemonStatusAction extends Action2 { constructor() { diff --git a/src/vs/workbench/contrib/modernity/browser/modernityDevMode.ts b/src/vs/workbench/contrib/modernity/browser/modernityDevMode.ts new file mode 100644 index 0000000000000..2f4949c768df5 --- /dev/null +++ b/src/vs/workbench/contrib/modernity/browser/modernityDevMode.ts @@ -0,0 +1,126 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Modernity Contributors. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { IContextKey, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js'; +import { IWorkbenchContribution } from '../../../common/contributions.js'; +import { IViewDescriptorService, ViewContainerLocation } from '../../../common/views.js'; +import { IWorkbenchLayoutService, Parts } from '../../../services/layout/browser/layoutService.js'; +import { IPaneCompositePartService } from '../../../services/panecomposite/browser/panecomposite.js'; + +export const MODERNITY_DEVELOPER_MODE_SETTING = 'modernity.developerMode'; + +export const MODERNITY_DEVELOPER_MODE_CONTEXT_KEY = new RawContextKey('modernityDeveloperMode', false); + +/** + * Built-in view containers unlocked in developer mode. They are hosted in the + * auxiliary bar next to the chat because the primary side bar is never + * restored: the left panel exposes custom extensions which can break the + * product (T278837441). + */ +const DEVELOPER_MODE_VIEW_CONTAINERS = [ + 'workbench.view.explorer', // file tree panel + 'workbench.view.search', + 'workbench.view.scm', + 'workbench.view.debug', +] as const; + +/** + * Swaps the workbench between the simple mode (locked chat panel) and the + * developer mode (code viewer, file tree, debugging, search, source control). + * + * Enforced in both modes, per T278837441: + * - the primary side bar and activity bar are never restored, so custom + * extensions cannot surface and break the product; + * - the panel part is never shown, so the terminal stays unreachable. + */ +export class ModernityDevModeContribution extends Disposable implements IWorkbenchContribution { + + static readonly ID = 'workbench.contrib.modernityDevMode'; + + private readonly developerModeContextKey: IContextKey; + + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService, + @IPaneCompositePartService private readonly paneCompositePartService: IPaneCompositePartService, + @IContextKeyService contextKeyService: IContextKeyService, + ) { + super(); + + this.developerModeContextKey = MODERNITY_DEVELOPER_MODE_CONTEXT_KEY.bindTo(contextKeyService); + + this.applyMode(); + + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(MODERNITY_DEVELOPER_MODE_SETTING)) { + this.applyMode(); + } + })); + + // The left panel and the terminal stay locked regardless of mode, so + // re-hide those parts whenever something tries to surface them. + this._register(this.layoutService.onDidChangePartVisibility(e => { + if (!e.visible) { + return; + } + if (e.partId === Parts.PANEL_PART || e.partId === Parts.SIDEBAR_PART || e.partId === Parts.ACTIVITYBAR_PART) { + this.layoutService.setPartHidden(true, e.partId as Parts); + } + })); + } + + private applyMode(): void { + const developerMode = this.configurationService.getValue(MODERNITY_DEVELOPER_MODE_SETTING) ?? false; + this.developerModeContextKey.set(developerMode); + if (developerMode) { + this.applyDeveloperMode(); + } else { + this.applySimpleMode(); + } + } + + /** + * Simple mode: the locked chat panel covers the entire screen. + */ + private applySimpleMode(): void { + // Park the developer view containers back at their default home. They + // stay unreachable because the side bar is never shown. + this.moveDeveloperViewContainers(ViewContainerLocation.Sidebar); + + this.layoutService.setPartHidden(true, Parts.ACTIVITYBAR_PART); + this.layoutService.setPartHidden(true, Parts.SIDEBAR_PART); + this.layoutService.setPartHidden(true, Parts.PANEL_PART); + this.layoutService.setAuxiliaryBarMaximized(true); + } + + /** + * Developer mode: code viewer plus file tree, debugging, search and source + * control next to the chat. The left panel and the terminal stay locked. + */ + private applyDeveloperMode(): void { + this.layoutService.setPartHidden(true, Parts.ACTIVITYBAR_PART); + this.layoutService.setPartHidden(true, Parts.SIDEBAR_PART); + this.layoutService.setPartHidden(true, Parts.PANEL_PART); + + this.layoutService.setAuxiliaryBarMaximized(false); + this.layoutService.setPartHidden(false, Parts.EDITOR_PART); + + this.moveDeveloperViewContainers(ViewContainerLocation.AuxiliaryBar); + this.layoutService.setPartHidden(false, Parts.AUXILIARYBAR_PART); + void this.paneCompositePartService.openPaneComposite(DEVELOPER_MODE_VIEW_CONTAINERS[0], ViewContainerLocation.AuxiliaryBar, false); + } + + private moveDeveloperViewContainers(location: ViewContainerLocation): void { + for (const id of DEVELOPER_MODE_VIEW_CONTAINERS) { + const container = this.viewDescriptorService.getViewContainerById(id); + if (container && this.viewDescriptorService.getViewContainerLocation(container) !== location) { + this.viewDescriptorService.moveViewContainerToLocation(container, location, undefined, MODERNITY_DEVELOPER_MODE_SETTING); + } + } + } +}