From 2142e7d7357e313b62f9f96d8588f876104e95fb Mon Sep 17 00:00:00 2001 From: RockChinQ Date: Mon, 8 Jun 2026 09:32:55 -0400 Subject: [PATCH] fix(web): show forced sandbox scope + make disabled tooltip tap-friendly When a SaaS deployment pins every pipeline to a fixed sandbox scope via system.limitation.force_box_session_id_template, the Sandbox Scope selector was correctly locked but still displayed the pipeline's stored value (e.g. the per-chat default), misrepresenting the scope that the runtime actually enforces on every exec. Coerce the displayed/saved value to the forced template so the locked selector truthfully shows the active scope (e.g. Global). Also fix the disabled_tooltip being invisible on touch devices: hover-only Radix tooltips never open without a pointer, so the explanation of why the field is locked could not be read on mobile. Wrap the info icon so a tap toggles the tooltip while desktop hover still works. --- .../dynamic-form/DynamicFormComponent.tsx | 42 +++++++++++++------ .../pipeline-form/PipelineFormComponent.tsx | 28 ++++++++++--- 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/web/src/app/home/components/dynamic-form/DynamicFormComponent.tsx b/web/src/app/home/components/dynamic-form/DynamicFormComponent.tsx index 078db6f7..057f4e52 100644 --- a/web/src/app/home/components/dynamic-form/DynamicFormComponent.tsx +++ b/web/src/app/home/components/dynamic-form/DynamicFormComponent.tsx @@ -198,6 +198,35 @@ function WebhookUrlField({ ); } +// Hover-only Radix tooltips never open on touch devices (no pointer hover), +// so the ``disabled_tooltip`` explaining why a field is locked was invisible on +// mobile. This wrapper makes the info icon also toggle the tooltip on tap while +// keeping hover behavior on desktop. +function DisabledTooltipIcon({ text }: { text: string }) { + const [open, setOpen] = useState(false); + return ( + + + + + + {text} + + + ); +} + export default function DynamicFormComponent({ itemConfigList, onSubmit, @@ -550,18 +579,7 @@ export default function DynamicFormComponent({ ? extractI18nObject(config.disabled_tooltip) : ''; const renderDisabledTooltipIcon = () => - disabledTooltip ? ( - - - - - - - {disabledTooltip} - - - - ) : null; + disabledTooltip ? : null; // Webhook URL fields are display-only; render outside of form binding if (config.type === 'webhook-url') { diff --git a/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx b/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx index fc0a6812..863c2202 100644 --- a/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx +++ b/web/src/app/home/pipelines/components/pipeline-form/PipelineFormComponent.tsx @@ -427,8 +427,9 @@ export default function PipelineFormComponent({ // 1. Box sandbox is unavailable, or // 2. the deployment pins all pipelines to a fixed scope via // ``system.limitation.force_box_session_id_template`` (SaaS). - const boxScopeForced = - !!systemInfo.limitation?.force_box_session_id_template; + const forcedBoxTemplate = + systemInfo.limitation?.force_box_session_id_template || ''; + const boxScopeForced = !!forcedBoxTemplate; const stageSystemContext = stage.name === 'local-agent' ? { @@ -437,6 +438,24 @@ export default function PipelineFormComponent({ } : undefined; + // When the deployment pins every pipeline to a fixed sandbox scope (SaaS + // ``force_box_session_id_template``), the Sandbox Scope selector is locked. + // The runtime already overrides the scope on every exec, but the stored + // pipeline value can be anything (e.g. the per-chat default), which would + // make the locked selector display a scope that is NOT the one actually in + // effect. Coerce the displayed/saved value to the forced template so the UI + // truthfully reflects runtime behavior. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const stageInitialValues: Record = + (form.watch(formName) as Record)?.[stage.name] || {}; + const effectiveInitialValues = + stage.name === 'local-agent' && boxScopeForced + ? { + ...stageInitialValues, + 'box-session-id-template': forcedBoxTemplate, + } + : stageInitialValues; + return ( @@ -450,10 +469,7 @@ export default function PipelineFormComponent({ )?.[stage.name] || {} - } + initialValues={effectiveInitialValues} onSubmit={(values) => { handleDynamicFormEmit(formName, stage.name, values); }}