Skip to content

Commit 0cba70e

Browse files
committed
Update webstudio settings logic
1 parent 2800f8c commit 0cba70e

15 files changed

Lines changed: 339 additions & 83 deletions

docs/webstudio/WEBSTUDIO_BUG_BACKLOG.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,3 +311,60 @@ Acceptance:
311311
- folder selection is stored in the parent Test Goals state
312312
- selecting a folder clears the selected YAML file after the unsaved-change guard is resolved
313313
- regression test `testGoalFolderSelectionState` passes
314+
315+
### WS-012 Save Buttons Stayed Disabled After Editor Changes
316+
317+
- Area: Test Configuration / CLI Mode
318+
- Status: fixed
319+
320+
Reproduction:
321+
322+
1. Open `Edit composition.properties` and modify the raw properties content.
323+
2. Observe that `Save composition.properties` remains disabled.
324+
3. Repeat with `Edit policies.properties`.
325+
4. Open CLI Mode, modify `Agent CLI Settings`, save once, then modify the settings again.
326+
327+
Expected:
328+
329+
- save buttons enable immediately when the corresponding editor content differs from the saved baseline
330+
- after saving Agent CLI settings, a later edit is detected as dirty again
331+
- unsaved-change guards and save-button enablement use the same dirty-state semantics
332+
333+
Bug:
334+
335+
- nested bound values changed inside child components without reliably invalidating parent dirty props
336+
- after saving Agent CLI settings, the editable settings object and saved baseline shared the same object reference, so later edits mutated both and were not detected
337+
338+
Acceptance:
339+
340+
- raw composition, raw policies, raw settings, and Agent CLI save buttons compute dirty state from local editor content against saved snapshots
341+
- Agent CLI settings save stores independent editable and saved object snapshots
342+
- regression test `editorDirtyState.test.js` passes
343+
344+
### WS-013 Java Source Modal Could Close During Save And Compile
345+
346+
- Area: Test Configuration / Java Composition and Java Policies
347+
- Status: fixed
348+
349+
Reproduction:
350+
351+
1. Open a Java composition or policy source editor modal.
352+
2. Modify the Java source.
353+
3. Click `Save and Compile`.
354+
4. Immediately click `Close` or click the modal backdrop.
355+
356+
Expected:
357+
358+
- source editor modal remains open while save and compile is running
359+
- `Close` is disabled during the operation
360+
- backdrop click-away is ignored during the operation
361+
- compile diagnostics remain visible after the operation completes
362+
363+
Bug:
364+
365+
- the modal close button and backdrop close handler were still active while save and compile was running
366+
367+
Acceptance:
368+
369+
- Java composition and policy source modals use the same close guard while `saving` is active
370+
- regression test `editorModalState.test.js` passes

docs/webstudio/WEBSTUDIO_FUNCTIONAL_SPEC.md

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ The configuration area exposes multiple editors:
4646
- `Edit policies.properties file`
4747
- `Edit Java Policies`
4848

49-
- `Edit test.settings file`
5049
- `Edit Settings`
5150

5251
- source editors for the related Composition and Policies Java files
@@ -71,15 +70,24 @@ The `Edit policies.properties file` view is treated as one logical editing area
7170
- `Edit Java Policies` renders the existing Java policies and lists them as active or available.
7271
The `Edit Java Policies` view is treated as one logical editing area for unsaved-change and uncompiled-change behavior.
7372

74-
### Settings Views
73+
### Settings View
7574

76-
The two settings views are:
75+
The settings editor is exposed as one left-sidebar entry:
7776

78-
- `Edit test.settings file` allows users to edit the file content directly.
79-
The `Edit test.settings file` view is treated as one logical editing area for unsaved-change behavior.
77+
- `Edit Settings`
78+
79+
The `Edit Settings` view has two internal representations of the same selected workspace `test.settings` data:
80+
81+
- visual settings form
82+
- raw `test.settings` text editor
83+
84+
The visual settings form is the default representation.
85+
86+
The user can switch between the two representations using a toggle inside the `Edit Settings` view header.
87+
88+
Both representations share the same `Save Settings` action.
8089

81-
- `Edit Settings` renders the content of the `test.settings` file into a visual grouped form of settings.
82-
The `Edit Settings` view is treated as one logical editing area for unsaved-change behavior.
90+
The `Edit Settings` view is treated as one logical editing area for unsaved-change behavior, regardless of which internal representation is active.
8391

8492
### CLI State Projection Mode
8593

@@ -266,35 +274,49 @@ Invalid compilation:
266274

267275
### Unsaved Settings Behavior
268276

269-
Unsaved settings protection applies to the settings editing areas.
277+
Unsaved settings protection applies to the `Edit Settings` view.
270278

271279
Protected views:
272280

273-
- `Edit test.settings file`
274281
- `Edit Settings`
275282

276-
The unsaved dialog must appear when leaving one of the settings editing areas, or switching between settings views while there are unsaved changes.
283+
The unsaved dialog must appear when leaving or toggling `Edit Settings` while there are unsaved changes.
277284

278285
It must appear when:
279286

280-
- leaving the settings configuration view for another top-level page while one of the settings views is dirty
281-
- changing workspace while one of the settings views is dirty
282-
- switching from a settings view to a non-settings configuration editor
283-
- switching from `Edit test.settings` to `Edit Settings`
284-
- switching from `Edit Settings` to `Edit test.settings`
287+
- leaving `Edit Settings` for another top-level page while settings are dirty
288+
- changing workspace while settings are dirty
289+
- switching from `Edit Settings` to a non-settings configuration editor
290+
- toggling between the visual settings form and raw `test.settings` text editor while settings are dirty
285291

286292
It must not appear when:
287293

288294
- switching to another view without changing anything
289-
- navigating to a settings view after the user already left the settings page
290-
- changing settings without leaving the `Edit Settings` form view
295+
- navigating to `Edit Settings` after the user already left the settings page
296+
- changing settings without leaving or toggling inside `Edit Settings`
291297

292298
Dialog actions:
293299

294300
- `Save`: persist current settings changes, then continue the pending navigation
295301
- `Discard`: restore persisted settings state, then continue the pending navigation
296302
- `Cancel`: keep the user in the current settings editor and abort the pending navigation
297303

304+
### Save Action Enablement
305+
306+
Configuration save actions are enabled only when their corresponding editor is dirty.
307+
308+
Examples:
309+
310+
- `Save Settings` is enabled only when the selected workspace settings differ from the persisted settings state
311+
- composition file save actions are enabled only when `composition.properties` differs from the persisted composition state
312+
- Java composition `Save and Compile` is enabled only when the selected Java composition source or composition flow state differs from the persisted/compiled state
313+
- policies file save actions are enabled only when `policies.properties` differs from the persisted policies state
314+
- Java policy `Save and Compile` is enabled only when the selected Java policy source or Java policies state differs from the persisted/compiled state
315+
316+
Dirty-state save enablement must use the same underlying change detection as the unsaved-change guards.
317+
318+
If a user attempts to leave a dirty editor without saving, the guard dialog must still appear even when the save action is enabled.
319+
298320
## Generate Mode
299321

300322
Generate Mode allows launching and stopping scriptless TESTAR execution.

docs/webstudio/WEBSTUDIO_UX_SPEC.md

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,7 @@ The left sidebar is the primary navigation for:
147147
- Java composition flow
148148
- policy files
149149
- Java policies
150-
- test settings
151-
- settings groups
150+
- Edit Settings
152151

153152
The default Test Configuration landing view is `Edit Java Composition Flow`.
154153

@@ -179,10 +178,18 @@ Expected behavior:
179178

180179
### Settings
181180

182-
The settings area has two representations of the same data:
181+
The left sidebar shows a single `Edit Settings` entry for workspace settings.
182+
183+
The `Edit Settings` panel has two representations of the same data:
184+
185+
- visual settings form
186+
- raw `test.settings` text editor
187+
188+
The visual settings form is shown by default.
189+
190+
A compact toggle near the `Edit Settings` heading switches between the visual form and raw text editor.
183191

184-
- raw `test.settings` text
185-
- structured `Edit Settings` form
192+
The toggle must not add a second left-sidebar entry and must not resize the surrounding layout.
186193

187194
Expected behavior:
188195

@@ -193,6 +200,21 @@ Expected behavior:
193200
- setting-specific feedback appears near the corresponding setting
194201
- restore actions only appear for settings that support restore behavior
195202
- enum settings use dropdowns with concrete values only and must not include a leading blank option
203+
- both settings representations share one `Save Settings` action
204+
- `Save Settings` is disabled while settings match the persisted state
205+
- `Save Settings` becomes enabled when either representation changes settings
206+
- toggling between representations while settings are dirty uses the same save/discard/cancel guard pattern
207+
208+
### Save Buttons
209+
210+
Save actions should communicate whether there is work to persist.
211+
212+
Expected behavior:
213+
214+
- save buttons are disabled when the corresponding editor has no detected changes
215+
- save buttons become enabled when the same dirty-state logic used by guard dialogs detects changes
216+
- disabled save buttons remain visible to preserve layout stability
217+
- guard dialogs still appear when users leave dirty editors without saving
196218

197219
## Runtime UX Contract
198220

webstudio/frontend/src/App.svelte

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import { shouldGuardConfigurationTransition } from "./configurationGuard.js";
1313
import { clearSelectedSourceState } from "./policyEditorState.js";
1414
import { stateModelWorkspaceDialog } from "./stateModelNavigation.js";
15+
import { objectSnapshot } from "./editorDirtyState.js";
1516
1617
const STATE_MODEL_URL = "http://localhost:8090/models";
1718
const CLI_AGENT_SETTING_KEYS = {
@@ -347,6 +348,16 @@
347348
}
348349
}
349350
351+
async function openVisualSettingsGroup(groupId) {
352+
await guardConfigurationTransition(async () => {
353+
openEditorImmediate("settings-form");
354+
selectedSettingsGroupId = groupId || selectedSettingsGroupId;
355+
if (!selectedSettingsGroupId && workspaceDocument?.settingsGroups?.length > 0) {
356+
selectedSettingsGroupId = workspaceDocument.settingsGroups[0].id;
357+
}
358+
}, "settings-form");
359+
}
360+
350361
async function openPoliciesProperties() {
351362
await openEditor("policies-properties");
352363
}
@@ -962,6 +973,14 @@
962973
return (selectedSourceFile.content || "") !== (savedSourceContents[selectedSourceFile.name] || "");
963974
}
964975
976+
function hasCurrentSelectedSourceChanges() {
977+
if (!selectedSourceFile?.category) {
978+
return false;
979+
}
980+
981+
return hasSelectedSourceChanges([selectedSourceFile.category]);
982+
}
983+
965984
function configurationDirtyAreas() {
966985
return {
967986
settings: hasSettingsChanges(),
@@ -1675,8 +1694,9 @@
16751694
currentEditorDocument = null;
16761695
} else if (selectedEditor === "test-settings") {
16771696
currentEditorDocument = {
1678-
title: "Edit test.settings file",
1679-
saveLabel: "Save test.settings",
1697+
title: "Edit Settings",
1698+
saveLabel: "Save Settings",
1699+
dirty: hasSettingsChanges(),
16801700
save: () => saveWorkspaceFile(
16811701
`/api/workspaces/${selectedWorkspaceName}/test-settings`,
16821702
workspaceDocument.testSettings.content
@@ -1685,13 +1705,15 @@
16851705
} else if (selectedEditor === "settings-form") {
16861706
currentEditorDocument = {
16871707
title: "Edit Settings",
1688-
saveLabel: "Save settings",
1708+
saveLabel: "Save Settings",
1709+
dirty: hasSettingsChanges(),
16891710
save: saveVisualSettings
16901711
};
16911712
} else if (selectedEditor === "policies-properties") {
16921713
currentEditorDocument = {
16931714
title: "Edit policies.properties",
16941715
saveLabel: "Save policies.properties",
1716+
dirty: hasPoliciesPropertiesChanges(),
16951717
save: () => saveWorkspaceFile(
16961718
`/api/workspaces/${selectedWorkspaceName}/policies-properties`,
16971719
workspaceDocument.policiesProperties.content
@@ -1701,6 +1723,7 @@
17011723
currentEditorDocument = {
17021724
title: "Edit composition.properties",
17031725
saveLabel: "Save composition.properties",
1726+
dirty: hasCompositionPropertiesChanges(),
17041727
save: () => saveWorkspaceFile(
17051728
`/api/workspaces/${selectedWorkspaceName}/composition-properties`,
17061729
workspaceDocument.compositionProperties.content
@@ -1710,16 +1733,13 @@
17101733
currentEditorDocument = {
17111734
title: selectedSourceFile.name,
17121735
saveLabel: "Save source",
1736+
dirty: hasSelectedSourceChanges([selectedSourceFile.category]),
17131737
save: saveSelectedSource
17141738
};
17151739
} else {
17161740
currentEditorDocument = null;
17171741
}
17181742
1719-
function selectSettingsGroup(groupId) {
1720-
selectedSettingsGroupId = groupId || "";
1721-
}
1722-
17231743
$: if (selectedEditor === "settings-form" && !selectedSettingsGroupId && workspaceDocument?.settingsGroups?.length > 0) {
17241744
selectedSettingsGroupId = workspaceDocument.settingsGroups[0].id;
17251745
}
@@ -2127,8 +2147,8 @@
21272147
setWorkspaceSettingByKey(CLI_AGENT_SETTING_KEYS.promptTitle, normalizedSettings.promptTitle);
21282148
setWorkspaceSettingByKey(CLI_AGENT_SETTING_KEYS.promptText, normalizedSettings.promptText);
21292149
await saveVisualSettings();
2130-
cliAgentSettings = normalizedSettings;
2131-
savedCliAgentSettings = normalizedSettings;
2150+
cliAgentSettings = objectSnapshot(normalizedSettings);
2151+
savedCliAgentSettings = objectSnapshot(normalizedSettings);
21322152
showTemporaryMessage("Agent CLI settings saved.");
21332153
return true;
21342154
} catch (cliError) {
@@ -2225,6 +2245,7 @@
22252245
openPoliciesProperties={openPoliciesProperties}
22262246
openTestSettings={openTestSettings}
22272247
openVisualSettings={openVisualSettings}
2248+
openVisualSettingsGroup={openVisualSettingsGroup}
22282249
policySourceFiles={policySourceFiles}
22292250
closeCompositionSourceEditor={closeCompositionSourceEditor}
22302251
closePolicySourceEditor={closePolicySourceEditor}
@@ -2234,15 +2255,19 @@
22342255
createPolicySource={createPolicySource}
22352256
javaCompileResult={javaCompileResult}
22362257
regexValidationResults={regexValidationResults}
2258+
savedCompositionPropertiesContent={savedCompositionPropertiesContent}
2259+
savedPoliciesPropertiesContent={savedPoliciesPropertiesContent}
2260+
savedTestSettingsContent={savedTestSettingsContent}
22372261
saving={saving}
22382262
setSettingValue={setSettingValue}
22392263
selectSource={selectSource}
22402264
selectCompositionFlowNode={selectCompositionFlowNode}
2241-
selectSettingsGroup={selectSettingsGroup}
22422265
selectedEditor={selectedEditor}
22432266
selectedCompositionFlowNode={selectedCompositionFlowNode}
22442267
selectedSettingsGroupId={selectedSettingsGroupId}
22452268
selectedSourceFile={selectedSourceFile}
2269+
selectedSourceDirty={hasCurrentSelectedSourceChanges()}
2270+
selectedSourceSavedContent={selectedSourceFile?.name ? savedSourceContents[selectedSourceFile.name] || "" : ""}
22462271
restoreSettingDefault={restoreSettingDefault}
22472272
togglePolicySourceActivation={togglePolicySourceActivation}
22482273
validateRegexExpression={validateRegexExpression}
@@ -2284,6 +2309,7 @@
22842309
<CliModeView
22852310
cliAgentSettings={cliAgentSettings}
22862311
cliStatus={cliStatus}
2312+
savedCliAgentSettings={savedCliAgentSettings}
22872313
saving={saving}
22882314
saveCliAgentSettings={saveCliAgentSettings}
22892315
selectedWorkspaceSutConnector={selectedWorkspaceSutConnector}

webstudio/frontend/src/CliModeView.svelte

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
<script>
2+
import { objectChanged } from "./editorDirtyState.js";
3+
24
export let cliAgentSettings = null;
35
export let cliStatus = null;
6+
export let savedCliAgentSettings = null;
47
export let saving = false;
58
export let selectedWorkspaceAvailableInCli = false;
69
export let selectedWorkspaceCliStateProjectionMode = "";
@@ -18,6 +21,7 @@
1821
$: manualSessionRunning = cliStatus?.status === "running" && cliStatus?.mode === "manual";
1922
$: agentSessionRunning = cliStatus?.status === "running" && cliStatus?.mode === "agent";
2023
$: cliSessionRunning = manualSessionRunning || agentSessionRunning;
24+
$: cliAgentSettingsDirty = objectChanged(cliAgentSettings, savedCliAgentSettings);
2125
2226
function startSession() {
2327
startCliManualSession();
@@ -192,7 +196,7 @@
192196
<label class="field-label" for="cli-agent-prompt">Prompt</label>
193197
<textarea id="cli-agent-prompt" bind:value={cliAgentSettings.promptText} rows="4"></textarea>
194198
<div class="button-row cli-mode-section-footer">
195-
<button type="button" on:click={saveCliAgentSettings} disabled={saving}>
199+
<button type="button" on:click={saveCliAgentSettings} disabled={saving || !cliAgentSettingsDirty}>
196200
Save Agent CLI Settings
197201
</button>
198202
</div>

0 commit comments

Comments
 (0)