You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@texera.apache.org by gi...@apache.org on 2026/07/09 00:36:37 UTC

(texera) branch main updated: feat(frontend): advanced settings for computing-unit creation (#6104)

This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/texera.git


The following commit(s) were added to refs/heads/main by this push:
     new 6fc1fb76eb feat(frontend): advanced settings for computing-unit creation (#6104)
6fc1fb76eb is described below

commit 6fc1fb76eb542fffcaed48823ed53bff91555901
Author: Kunwoo (Chris) <14...@users.noreply.github.com>
AuthorDate: Wed Jul 8 20:24:51 2026 -0400

    feat(frontend): advanced settings for computing-unit creation (#6104)
    
    ### What changes were proposed in this PR?
    
    This PR improves the UI of creating a computing unit (CU) with an
    "Advanced Settings" section to get a simplified default view.
    
    The shared memory size and JVM memory size are too technical for most
    users, so the create-CU modal now shows only the common fields (name /
    RAM / CPU) by default and tucks those two options behind a collapsed
    "Advanced Settings" panel:
    
    - Collapsed by default, and collapses again every time the modal opens.
    - If shared memory is set larger than total memory while the panel is
    collapsed, a warning appears on the panel header so the invalid value is
    not hidden.
    - Implemented once in the shared `ComputingUnitCreateModalComponent`
    (#6082), so both entry points (the dashboard Computing Units page and
    the workspace power button) get the same behavior.
    
    The feature was originally implemented on the downstream dknet-ai.org
    deployment
    ([Texera/dknet-ai-public@30318e2d](https://github.com/Texera/dknet-ai-public/commit/30318e2da47aff5620997febdf2f92e54c92aad1)).
    
    ### Any related issues, documentation, discussions?
    
    Closes #6102
    
    ### How was this PR tested?
    
    
    https://github.com/user-attachments/assets/b7a5e4d3-b655-45bc-bf5c-f6ef1674af9c
    
    ### Was this PR authored or co-authored using generative AI tooling?
    
    Generated-by: Claude Code
    
    ---------
    
    Signed-off-by: Kunwoo (Chris) <14...@users.noreply.github.com>
    Co-authored-by: Copilot Autofix powered by AI <17...@users.noreply.github.com>
---
 .../computing-unit-create-modal.component.html     | 148 +++++++++++++--------
 .../computing-unit-create-modal.component.scss     | 141 +++++++++++++++++++-
 .../computing-unit-create-modal.component.spec.ts  | 135 ++++++++++++++++++-
 .../computing-unit-create-modal.component.ts       |  58 +++++++-
 4 files changed, 417 insertions(+), 65 deletions(-)

diff --git a/frontend/src/app/common/component/computing-unit-create-modal/computing-unit-create-modal.component.html b/frontend/src/app/common/component/computing-unit-create-modal/computing-unit-create-modal.component.html
index df5f725c9c..462f506dbd 100644
--- a/frontend/src/app/common/component/computing-unit-create-modal/computing-unit-create-modal.component.html
+++ b/frontend/src/app/common/component/computing-unit-create-modal/computing-unit-create-modal.component.html
@@ -93,65 +93,103 @@
           </nz-select>
         </div>
 
-        <div class="select-unit shared-memory-group">
-          <span>
-            Adjust the Shared Memory Size
+        <!-- Advanced settings: progressive disclosure for the power-user knobs -->
+        <div class="advanced-settings">
+          <button
+            type="button"
+            class="advanced-settings-toggle"
+            [class.expanded]="showAdvancedSettings"
+            [attr.aria-expanded]="showAdvancedSettings"
+            (click)="toggleAdvancedSettings()">
             <i
               nz-icon
-              nzType="info-circle"
-              nz-tooltip
-              [nzTooltipTitle]="
-                'Shared memory (/dev/shm) is used for inter-process communication. If you plan to run ML tasks (e.g., using PyTorch), consider increasing this size. Learn more: https://man7.org/linux/man-pages/man7/shm_overview.7.html'
-              ">
-            </i>
-          </span>
-          <div class="shm-input-row">
-            <input
-              nz-input
-              type="number"
-              min="0"
-              [(ngModel)]="shmSizeValue"
-              class="shm-size-input"
-              placeholder="e.g. 512" />
-            <nz-select
-              class="shm-unit-select"
-              [(ngModel)]="shmSizeUnit">
-              <nz-option
-                nzValue="Mi"
-                nzLabel="Mi"></nz-option>
-              <nz-option
-                nzValue="Gi"
-                nzLabel="Gi"></nz-option>
-            </nz-select>
-          </div>
-          <div
-            *ngIf="isShmTooLarge()"
-            class="shm-warning">
-            Shared memory cannot be greater than total memory.
-          </div>
-        </div>
+              nzType="right"
+              nzTheme="outline"
+              class="advanced-settings-chevron"></i>
+            <span class="advanced-settings-label">Advanced Settings</span>
+            <span
+              class="advanced-settings-hint"
+              [class.advanced-settings-hint--warning]="hasCollapsedWarning()">
+              <ng-container *ngIf="hasCollapsedWarning(); else advancedSettingsHint">
+                <i
+                  nz-icon
+                  nzType="exclamation-circle"
+                  nzTheme="outline"></i>
+                {{ collapsedWarningText() }}
+              </ng-container>
+              <ng-template #advancedSettingsHint>Shared memory · JVM heap size</ng-template>
+            </span>
+          </button>
 
-        <div class="select-unit name-field">
-          <span>JVM Memory Size: {{selectedJvmMemorySize}}</span>
-          <nz-slider
-            *ngIf="showJvmMemorySlider"
-            class="jvm-memory-slider"
-            [nzMarks]="jvmMemoryMarks"
-            [nzMin]="jvmMemorySteps[0] || 2"
-            [nzMax]="jvmMemoryMax"
-            [nzStep]="null"
-            [nzIncluded]="false"
-            [ngModel]="jvmMemorySliderValue"
-            (ngModelChange)="onJvmMemorySliderChange($event)">
-          </nz-slider>
           <div
-            *ngIf="isMaxJvmMemorySelected()"
-            class="memory-warning">
-            <nz-alert
-              nzType="warning"
-              nzMessage="Using maximum JVM memory may affect the performance of Python-based operators."
-              nzShowIcon>
-            </nz-alert>
+            class="advanced-settings-body"
+            [class.expanded]="showAdvancedSettings"
+            [attr.inert]="showAdvancedSettings ? null : ''">
+            <div class="advanced-settings-clip">
+              <div class="advanced-settings-inner">
+                <div class="select-unit shared-memory-group">
+                  <span>
+                    Adjust the Shared Memory Size
+                    <i
+                      nz-icon
+                      nzType="info-circle"
+                      nz-tooltip
+                      [nzTooltipTitle]="
+                        'Shared memory (/dev/shm) is used for inter-process communication. If you plan to run ML tasks (e.g., using PyTorch), consider increasing this size. Learn more: https://man7.org/linux/man-pages/man7/shm_overview.7.html'
+                      ">
+                    </i>
+                  </span>
+                  <div class="shm-input-row">
+                    <input
+                      nz-input
+                      type="number"
+                      min="0"
+                      [(ngModel)]="shmSizeValue"
+                      class="shm-size-input"
+                      placeholder="e.g. 512" />
+                    <nz-select
+                      class="shm-unit-select"
+                      [(ngModel)]="shmSizeUnit">
+                      <nz-option
+                        nzValue="Mi"
+                        nzLabel="Mi"></nz-option>
+                      <nz-option
+                        nzValue="Gi"
+                        nzLabel="Gi"></nz-option>
+                    </nz-select>
+                  </div>
+                  <div
+                    *ngIf="isShmTooLarge()"
+                    class="shm-warning">
+                    Shared memory cannot be greater than total memory.
+                  </div>
+                </div>
+
+                <div class="select-unit jvm-memory-group">
+                  <span>JVM Memory Size: {{selectedJvmMemorySize}}</span>
+                  <nz-slider
+                    *ngIf="showJvmMemorySlider"
+                    class="jvm-memory-slider"
+                    [nzMarks]="jvmMemoryMarks"
+                    [nzMin]="jvmMemorySteps[0] || 2"
+                    [nzMax]="jvmMemoryMax"
+                    [nzStep]="null"
+                    [nzIncluded]="false"
+                    [ngModel]="jvmMemorySliderValue"
+                    (ngModelChange)="onJvmMemorySliderChange($event)">
+                  </nz-slider>
+                  <div
+                    *ngIf="isMaxJvmMemorySelected()"
+                    class="memory-warning">
+                    <nz-alert
+                      nzType="warning"
+                      nzMessage="Using maximum JVM memory may affect the performance of Python-based operators."
+                      nzShowIcon>
+                    </nz-alert>
+                  </div>
+                </div>
+              </div>
+            </div>
           </div>
         </div>
       </ng-container>
diff --git a/frontend/src/app/common/component/computing-unit-create-modal/computing-unit-create-modal.component.scss b/frontend/src/app/common/component/computing-unit-create-modal/computing-unit-create-modal.component.scss
index ba2b25415b..d519455a90 100644
--- a/frontend/src/app/common/component/computing-unit-create-modal/computing-unit-create-modal.component.scss
+++ b/frontend/src/app/common/component/computing-unit-create-modal/computing-unit-create-modal.component.scss
@@ -23,11 +23,6 @@
   width: 100%;
 }
 
-.jvm-memory-slider {
-  width: 100%;
-  margin: 10px 0;
-}
-
 .memory-warning {
   margin-top: 10px;
   font-size: 0.9em;
@@ -86,3 +81,139 @@
     white-space: nowrap;
   }
 }
+
+// ── Advanced Settings progressive-disclosure panel ──────────────────────────
+// Collapsed by default; houses the shared-memory and JVM-heap knobs so the
+// common path (name / RAM / CPU) stays uncluttered.
+
+// The inner panel's padding, also reused for the slider's bottom margin.
+$advanced-panel-padding: 16px;
+
+.advanced-settings {
+  grid-column: span 2;
+  margin-top: 2px;
+  border-top: 1px solid #f0f0f0;
+  padding-top: 10px;
+}
+
+.advanced-settings-toggle {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  width: 100%;
+  padding: 7px 10px;
+  margin: 0;
+  background: transparent;
+  border: none;
+  border-radius: 6px;
+  color: #595959;
+  font-family: inherit;
+  cursor: pointer;
+  transition:
+    background-color 0.2s ease,
+    color 0.2s ease;
+
+  &:hover {
+    background-color: #f0f6ff;
+    color: #1890ff;
+  }
+
+  &:focus-visible {
+    outline: 2px solid rgba(24, 144, 255, 0.35);
+    outline-offset: 1px;
+  }
+}
+
+.advanced-settings-chevron {
+  font-size: 11px;
+  color: inherit;
+  transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1);
+
+  .advanced-settings-toggle.expanded & {
+    transform: rotate(90deg);
+  }
+}
+
+.advanced-settings-label {
+  font-size: 13px;
+  font-weight: 600;
+  letter-spacing: 0.01em;
+  color: inherit;
+}
+
+.advanced-settings-hint {
+  display: inline-flex;
+  align-items: center;
+  margin-left: auto;
+  font-size: 12px;
+  font-weight: 400;
+  color: #bfbfbf;
+  white-space: nowrap;
+  transition: color 0.2s ease;
+
+  .advanced-settings-toggle.expanded &,
+  .advanced-settings-toggle:hover & {
+    color: #69b1ff;
+  }
+}
+
+// Warning variant: promotes a hidden shared-memory error onto the collapsed
+// header. Stays amber even on hover, overriding the blue affordance above.
+.advanced-settings-hint--warning,
+.advanced-settings-toggle:hover .advanced-settings-hint--warning {
+  color: #faad14;
+  font-weight: 500;
+
+  i {
+    margin-right: 4px;
+  }
+}
+
+// Height animates via the 0fr→1fr grid-row trick so it eases open to the
+// content's natural height without a hard-coded max-height.
+.advanced-settings-body {
+  display: grid;
+  grid-template-rows: 0fr;
+  transition: grid-template-rows 0.28s cubic-bezier(0.4, 0, 0.2, 1);
+
+  &.expanded {
+    grid-template-rows: 1fr;
+  }
+}
+
+.advanced-settings-clip {
+  overflow: hidden;
+  min-height: 0;
+}
+
+.advanced-settings-inner {
+  display: flex;
+  flex-direction: column;
+  gap: 18px;
+  margin-top: 10px;
+  padding: $advanced-panel-padding;
+  border: 1px solid #eef0f2;
+  border-left: 2px solid #1890ff;
+  border-radius: 8px;
+  background: #fafbfc;
+  // Content drifts up + fades in slightly behind the height ease for depth.
+  opacity: 0;
+  transform: translateY(-4px);
+  transition:
+    opacity 0.26s ease 0.04s,
+    transform 0.26s ease 0.04s;
+
+  .advanced-settings-body.expanded & {
+    opacity: 1;
+    transform: translateY(0);
+  }
+
+  .jvm-memory-slider {
+    // align-self: stretch makes the horizontal margin the single source of
+    // truth for the inset. The bottom margin mirrors the panel padding so the
+    // slider's end labels (e.g. 2G / 16G) keep the same breathing room as
+    // every other edge of the box.
+    align-self: stretch;
+    margin: 6px 10px $advanced-panel-padding;
+  }
+}
diff --git a/frontend/src/app/common/component/computing-unit-create-modal/computing-unit-create-modal.component.spec.ts b/frontend/src/app/common/component/computing-unit-create-modal/computing-unit-create-modal.component.spec.ts
index 027c62ee7a..1b0782ca9a 100644
--- a/frontend/src/app/common/component/computing-unit-create-modal/computing-unit-create-modal.component.spec.ts
+++ b/frontend/src/app/common/component/computing-unit-create-modal/computing-unit-create-modal.component.spec.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import { ApplicationRef, DebugElement, getDebugNode } from "@angular/core";
+import { ApplicationRef, DebugElement, getDebugNode, SimpleChange } from "@angular/core";
 import { NgModel } from "@angular/forms";
 import { CdkVirtualScrollViewport } from "@angular/cdk/scrolling";
 import { ComponentFixture, TestBed } from "@angular/core/testing";
@@ -220,6 +220,94 @@ describe("ComputingUnitCreateModalComponent", () => {
     expect(component.isShmTooLarge()).toBe(false);
   });
 
+  it("toggles the advanced-settings panel", () => {
+    component.showAdvancedSettings = false;
+    component.toggleAdvancedSettings();
+    expect(component.showAdvancedSettings).toBe(true);
+    component.toggleAdvancedSettings();
+    expect(component.showAdvancedSettings).toBe(false);
+  });
+
+  it("re-collapses the advanced-settings panel each time the modal opens", () => {
+    component.showAdvancedSettings = true;
+    component.ngOnChanges({ visible: new SimpleChange(false, true, false) });
+    expect(component.showAdvancedSettings).toBe(false);
+
+    // Closing the modal must not touch the panel state.
+    component.showAdvancedSettings = true;
+    component.ngOnChanges({ visible: new SimpleChange(true, false, false) });
+    expect(component.showAdvancedSettings).toBe(true);
+  });
+
+  it("resets the advanced values each time the modal opens", () => {
+    fixture.detectChanges();
+    component.selectedMemory = "8Gi";
+    component.onMemorySelectionChange();
+    component.shmSizeValue = 4;
+    component.shmSizeUnit = "Gi";
+    component.onJvmMemorySliderChange(component.jvmMemoryMax);
+
+    component.ngOnChanges({ visible: new SimpleChange(false, true, false) });
+
+    const expected = getJvmMemorySliderConfig("8Gi");
+    expect(component.shmSizeValue).toBe(64);
+    expect(component.shmSizeUnit).toBe("Mi");
+    expect(component.jvmMemorySliderValue).toBe(expected.jvmMemorySliderValue);
+    expect(component.selectedJvmMemorySize).toBe(expected.selectedJvmMemorySize);
+
+    // Closing the modal must not touch the values.
+    component.shmSizeValue = 2;
+    component.shmSizeUnit = "Gi";
+    component.ngOnChanges({ visible: new SimpleChange(true, false, false) });
+    expect(component.shmSizeValue).toBe(2);
+    expect(component.shmSizeUnit).toBe("Gi");
+  });
+
+  it("surfaces the max-JVM warning on the collapsed header", () => {
+    vi.spyOn(component, "isShmTooLarge").mockReturnValue(false);
+    vi.spyOn(component, "isMaxJvmMemorySelected").mockReturnValue(true);
+
+    component.showAdvancedSettings = false;
+    expect(component.hasCollapsedWarning()).toBe(true);
+    expect(component.collapsedWarningText()).toBe("JVM memory at maximum");
+
+    // Expanded: the inline nz-alert is visible, so the header stays quiet.
+    component.showAdvancedSettings = true;
+    expect(component.hasCollapsedWarning()).toBe(false);
+  });
+
+  it("prefers the shm warning over the JVM warning when both apply", () => {
+    vi.spyOn(component, "isShmTooLarge").mockReturnValue(true);
+    vi.spyOn(component, "isMaxJvmMemorySelected").mockReturnValue(true);
+
+    component.selectedMemory = "4Gi";
+    component.showAdvancedSettings = false;
+    expect(component.collapsedWarningText()).toBe("Shared memory exceeds total");
+  });
+
+  it("flags a collapsed warning only when collapsed and shm exceeds memory", () => {
+    vi.spyOn(component, "isShmTooLarge").mockReturnValue(true);
+    component.selectedMemory = "4Gi";
+    component.showAdvancedSettings = false;
+    expect(component.hasCollapsedWarning()).toBe(true);
+    // Expanded: the inline warning is visible, so the header stays quiet.
+    component.showAdvancedSettings = true;
+    expect(component.hasCollapsedWarning()).toBe(false);
+  });
+
+  it("suppresses the shm warning until the memory options have loaded", () => {
+    vi.spyOn(component, "isShmTooLarge").mockReturnValue(true);
+    component.selectedMemory = "";
+    component.showAdvancedSettings = false;
+    expect(component.hasCollapsedWarning()).toBe(false);
+  });
+
+  it("does not flag a warning when shm fits within memory", () => {
+    vi.spyOn(component, "isShmTooLarge").mockReturnValue(false);
+    component.showAdvancedSettings = false;
+    expect(component.hasCollapsedWarning()).toBe(false);
+  });
+
   it("initializes the local computing unit URI from the window location", () => {
     fixture.detectChanges();
     expect(component.localComputingUnitUri).toBe(buildLocalComputingUnitUri(window.location));
@@ -346,6 +434,17 @@ describe("ComputingUnitCreateModalComponent", () => {
       expect(document.querySelector(".create-compute-unit-container")).toBeTruthy();
       expect(document.querySelector(".unit-name-input")).toBeTruthy();
       expect(document.querySelector(".gpu-selection")).toBeTruthy();
+
+      // Collapsed: the invalid shm value is announced on the panel header.
+      expect(document.querySelector(".advanced-settings-hint--warning")?.textContent).toContain(
+        "Shared memory exceeds total"
+      );
+
+      // Expand the panel so the advanced controls are actually user-visible
+      // before asserting on them.
+      (document.querySelector(".advanced-settings-toggle") as HTMLButtonElement).click();
+      fixture.detectChanges();
+
       expect(document.querySelector(".shm-warning")?.textContent).toContain(
         "Shared memory cannot be greater than total memory."
       );
@@ -354,6 +453,35 @@ describe("ComputingUnitCreateModalComponent", () => {
       expect(document.querySelector(".unit-uri-input")).toBeNull();
     });
 
+    it("expands and collapses the advanced panel when the toggle button is clicked", () => {
+      mockComputingUnitService.getComputingUnitTypes.mockReturnValue(
+        of({ typeOptions: ["kubernetes"] as WorkflowComputingUnitType[] })
+      );
+      mockComputingUnitService.getComputingUnitLimitOptions.mockReturnValue(
+        of({ cpuLimitOptions: ["1"], memoryLimitOptions: ["1Gi"], gpuLimitOptions: ["0"] })
+      );
+      fixture.detectChanges();
+      component.visible = true;
+      fixture.detectChanges();
+
+      const toggle = document.querySelector(".advanced-settings-toggle") as HTMLButtonElement;
+      const body = document.querySelector(".advanced-settings-body") as HTMLElement;
+      expect(toggle.getAttribute("aria-expanded")).toBe("false");
+      expect(body.classList.contains("expanded")).toBe(false);
+
+      toggle.click();
+      fixture.detectChanges();
+      expect(component.showAdvancedSettings).toBe(true);
+      expect(toggle.getAttribute("aria-expanded")).toBe("true");
+      expect(body.classList.contains("expanded")).toBe(true);
+
+      toggle.click();
+      fixture.detectChanges();
+      expect(component.showAdvancedSettings).toBe(false);
+      expect(toggle.getAttribute("aria-expanded")).toBe("false");
+      expect(body.classList.contains("expanded")).toBe(false);
+    });
+
     it("renders the minimal kubernetes form without GPU select, warnings, or slider", () => {
       mockComputingUnitService.getComputingUnitTypes.mockReturnValue(
         of({ typeOptions: ["kubernetes"] as WorkflowComputingUnitType[] })
@@ -429,6 +557,11 @@ describe("ComputingUnitCreateModalComponent", () => {
       setInputValue(".unit-name-input", "Typed Name");
       expect(component.newComputingUnitName).toBe("Typed Name");
 
+      // Expand the advanced panel first: the shm input and JVM slider sit in
+      // the collapsed (inert) body, which a real user cannot interact with.
+      (document.querySelector(".advanced-settings-toggle") as HTMLButtonElement).click();
+      tick();
+
       setInputValue(".shm-size-input", "128");
       expect(component.shmSizeValue).toBe(128);
 
diff --git a/frontend/src/app/common/component/computing-unit-create-modal/computing-unit-create-modal.component.ts b/frontend/src/app/common/component/computing-unit-create-modal/computing-unit-create-modal.component.ts
index 930bde396b..5ae0ccbedd 100644
--- a/frontend/src/app/common/component/computing-unit-create-modal/computing-unit-create-modal.component.ts
+++ b/frontend/src/app/common/component/computing-unit-create-modal/computing-unit-create-modal.component.ts
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-import { Component, EventEmitter, Input, OnInit, Output } from "@angular/core";
+import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from "@angular/core";
 import { NgFor, NgIf, TitleCasePipe } from "@angular/common";
 import { FormsModule } from "@angular/forms";
 import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy";
@@ -46,6 +46,10 @@ import {
   unitTypeMessageTemplate,
 } from "../../util/computing-unit.util";
 
+// Defaults for the advanced-settings values, restored every time the modal opens.
+const DEFAULT_SHM_SIZE_VALUE = 64;
+const DEFAULT_SHM_SIZE_UNIT: "Mi" | "Gi" = "Mi";
+
 /**
  * The "create computing unit" modal shared by the workspace power button and
  * the dashboard Computing Units page. Owns the whole form (type, name,
@@ -79,11 +83,15 @@ import {
     TitleCasePipe,
   ],
 })
-export class ComputingUnitCreateModalComponent implements OnInit {
+export class ComputingUnitCreateModalComponent implements OnInit, OnChanges {
   @Input() visible = false;
   @Output() visibleChange = new EventEmitter<boolean>();
   @Output() unitCreated = new EventEmitter<DashboardWorkflowComputingUnit>();
 
+  // Advanced settings disclosure — collapsed by default, houses the
+  // shared-memory and JVM-heap knobs most users never touch.
+  showAdvancedSettings = false;
+
   newComputingUnitName: string = "";
   selectedMemory: string = "";
   selectedCpu: string = "";
@@ -91,8 +99,8 @@ export class ComputingUnitCreateModalComponent implements OnInit {
   selectedJvmMemorySize: string = "1G"; // Initial JVM memory size
   selectedComputingUnitType?: WorkflowComputingUnitType; // Selected computing unit type
   selectedShmSize: string = "64Mi"; // Shared memory size
-  shmSizeValue: number = 64; // default to 64
-  shmSizeUnit: "Mi" | "Gi" = "Mi"; // default unit
+  shmSizeValue: number = DEFAULT_SHM_SIZE_VALUE;
+  shmSizeUnit: "Mi" | "Gi" = DEFAULT_SHM_SIZE_UNIT;
   availableComputingUnitTypes: WorkflowComputingUnitType[] = [];
   localComputingUnitUri: string = ""; // URI for local computing unit
 
@@ -157,6 +165,48 @@ export class ComputingUnitCreateModalComponent implements OnInit {
       });
   }
 
+  ngOnChanges(changes: SimpleChanges): void {
+    if (changes["visible"]?.currentValue === true) {
+      this.resetAdvancedSettings();
+    }
+  }
+
+  // Runs every time the modal opens: re-collapse the advanced panel so it
+  // always starts on the simple path (name / RAM / CPU), and restore the
+  // advanced values so a setting from a previous open can't ride along
+  // hidden behind the collapsed panel.
+  private resetAdvancedSettings(): void {
+    this.showAdvancedSettings = false;
+    this.shmSizeValue = DEFAULT_SHM_SIZE_VALUE;
+    this.shmSizeUnit = DEFAULT_SHM_SIZE_UNIT;
+    this.resetJvmMemorySlider();
+  }
+
+  toggleAdvancedSettings(): void {
+    this.showAdvancedSettings = !this.showAdvancedSettings;
+  }
+
+  // Surfaces panel warnings on the collapsed header so an invalid or risky
+  // value tucked inside the panel can't be submitted unseen. The invalid shm
+  // value outranks the advisory max-JVM note when both apply. The shm check
+  // is skipped until the memory options have loaded (selectedMemory is empty).
+  collapsedWarningText(): string | null {
+    if (this.showAdvancedSettings) {
+      return null;
+    }
+    if (this.selectedMemory !== "" && this.isShmTooLarge()) {
+      return "Shared memory exceeds total";
+    }
+    if (this.isMaxJvmMemorySelected()) {
+      return "JVM memory at maximum";
+    }
+    return null;
+  }
+
+  hasCollapsedWarning(): boolean {
+    return this.collapsedWarningText() !== null;
+  }
+
   // Determines if the GPU selection dropdown should be shown
   showGpuSelection(): boolean {
     // Don't show GPU selection if there are no options or only "0" option