Skip to content

fix(workflow-form): give the Form View a code editor host, so its step panel's code button opens the editor - #9023

Open
yangzhang75 wants to merge 1 commit into
apache:mainfrom
yangzhang75:fix/form-code-editor-host
Open

yangzhang75 wants to merge 1 commit into
apache:mainfrom
yangzhang75:fix/form-code-editor-host

Conversation

@yangzhang75

@yangzhang75 yangzhang75 commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

What changes were proposed in this PR?

Closes #8760. Part of the Form View feature (#8011); from the #8439 refinements (item 1).

The code editor renders into an outlet the operator canvas published once (<ng-template #codeEditor> in WorkspaceComponent, handed to CodeEditorService as vc) and never let go of. The Form View has no outlet of its own, yet its step panel shows the operator's real property frame and, in edit mode, the ordinary Edit code content button. Pressing it opened the editor into whatever vc held: nothing, from a direct open of the Form View, or the destroyed canvas's outlet after the routed switch between the views (#8581), where Monaco rendered into a detached view and nothing appeared. Verified on main before changing anything: the button is there and enabled, the click does nothing, and no error is raised.

Each view that shows operator properties now carries its own outlet, marked texeraCodeEditorHost on an <ng-template> in its template:

  • CodeEditorHostDirective attaches the outlet to CodeEditorService when the view's template is built and detaches it -- only its own, so a host that has since replaced it stays -- when the view is destroyed, which also closes an editor still open in it (the view's teardown destroys what its outlet holds before the hook runs). The views are sibling routes and never overlap, so there is one host at a time.
  • The canvas uses the directive in place of its @ViewChild + vc hand-over; the Form View gets the same one line. The service exposes host, attachHost and detachHost in place of the public vc.
  • The code button opens into whichever host is up, and guards the case of none: every view that mounts a codearea hosts the editor now, so this is a guard rather than a state the button shows -- a co-editor's open reaching a codearea with no host neither throws nor marks that operator's editor open. A co-editor's open also reaches only the codearea on that operator.
  • The editor is told its operator by its opener (currentOperatorId is an input) instead of reading the highlight in its constructor, and may be dragged within the Form View's page as well as the canvas (cdkDragBoundary). The Form View lifts the editor above its sticky title row.
  • Outside edit mode the Form View mounts the step panel as a read-only inspection -- inert, so nothing in it can be pressed, the code button included. That button used to read "View code content" there, as it does on the canvas for a workflow one cannot write, where it still opens a read-only editor; here it could not open anything, so it offered a view it could not deliver. The frame now marks the code field of such a mount (props.inspect), and the button keeps its usual face -- "Edit code content", greyed out with the rest of the panel -- which is also what the canvas shows for the same step. The canvas's read-only "View code content", which does work, is unchanged.

Deliberately not in this change: exposing a code property as a form input. The form's rules keep the code editor out of what an author may expose (NON_FORM_FIELD_TYPES) and fall back from it on an old workflow that carries one (CANVAS_ONLY_FORMLY_TYPES) -- editing code is not filling in a value, and a form reader should not be writing code. The step panel is the author's own editing surface, and that is what this fixes.

Any related issues, documentation, discussions?

Closes #8760. Part of #8439 (item 1, raised by @mengw15 on #8436) and the Form View feature #8011. The routed switch that left the canvas's outlet destroyed under the Form View is #8581.

How was this PR tested?

Unit tests (vitest). The directive's spec: attaches its outlet on construction, closes what it hosts and lets go of the host on destroy, and does not let go of a host that has since replaced it. The service's spec: host/attachHost/detachHost, including that detaching a stale host leaves the current one. The code button's spec: opens into the host and tells the editor its operator, which is the highlighted one; sets only inputs the editor declares; stays closed when a co-editor opens another operator's editor; and, without a host, opens nothing and marks nothing open on a click, and stays closed instead of throwing when a co-editor opens theirs. The editor's spec: takes its operator as an input and still highlights it on focus; a read-only control gets no context-menu actions. The button's label: "Edit code content" while editable; "View code content" while read-only and still pressable (the canvas); "Edit code content", greyed, on a read-only inspection that cannot press it; and the frame's spec pins that the inspection mark is set exactly when the frame is not acting as the editor. The Form View's rendered spec: hosts the editor while the page is up and lets go of the host when it is destroyed; the workspace's spec: the same through the directive instead of the old @ViewChild.

By hand, on two dev builds of the same base commit, one with this change and one without, as the workflow's owner in the Form View's edit mode with the workflow strip open and the Python UDF step selected: on main, Edit code content is enabled and the click opens nothing. On this branch, the click opens the editor as a floating box titled with the step's name, showing its code, over the form page (a Monaco instance in the page, inside the Form View's own subtree), and the button reads as open while it is.

Each guard was deletion-checked and turned a named test red: the form's outlet, the detach on destroy, and the button's no-host guard (that one fails to compile without it). eslint, prettier and the production (AOT) build pass; every statement, function and branch this PR adds is covered; the full frontend suite passes.

Video

Before

Screen.Recording.2026-10-19.at.12.16.50.AM.mov

After

Screen.Recording.2026-10-19.at.12.17.10.AM.mov

Was this PR authored or co-authored using generative AI tooling?

Yes. Generated-by: Claude Code (Anthropic). Co-authored with Claude, reviewed line by line by the author before submission.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FVvP3ttj22f9LB4p9u2anY

@yangzhang75
yangzhang75 force-pushed the fix/form-code-editor-host branch from 3d3d280 to facd072 Compare October 19, 2026 06:52
@github-actions github-actions Bot added fix frontend Changes related to the frontend GUI labels Oct 19, 2026
@Yicong-Huang Yicong-Huang added release/v1.3 back porting to release/v1.3 release/v1.2 back porting to release/v1.2 labels Oct 19, 2026
@github-actions
github-actions Bot requested review from mengw15 and xuang7 October 19, 2026 06:56
@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Backport auto-label report

This fix: PR was checked against each actively-supported release branch. A release/* label nominates a backport target; the branch's release manager approving this PR is what sends the fix there. The required Backport Approvals check stays red until every label below is approved, so each manager either approves or removes their own label — which is why the labels left on a merged PR are exactly the branches it reached.

Release branch Analysis
✅ release/v1.3 Already labeled — this fix is queued to backport here. @mengw15 decides: approving sends the fix here, removing this label declines it. The merge waits on one or the other.
✅ release/v1.2 Already labeled — this fix is queued to backport here. @xuang7 decides: approving sends the fix here, removing this label declines it. The merge waits on one or the other.

Auto-label run.

@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Automated Reviewer Suggestions

Based on the git blame history of the changed files, we recommend the following reviewers:

  • Contributors with relevant context: @aglinxinyuan, @mengw15, @carloea2
    You can notify them by mentioning @aglinxinyuan, @mengw15, @carloea2 in a comment.

…p panel's code button opens the editor

The code editor renders into an outlet the operator canvas published once
(`<ng-template #codeEditor>` in WorkspaceComponent, handed to CodeEditorService
as `vc`) and never let go of. The Form View has no outlet of its own, yet its
step panel shows the operator's real property frame and, in edit mode, the
ordinary "Edit code content" button. Pressing it opened the editor into
whatever `vc` held: nothing, from a direct open of the Form View, or the
destroyed canvas's outlet after the routed switch between the views (apache#8581),
where Monaco rendered into a detached view and nothing appeared on screen.
Verified on main: the button is there, the click does nothing, no error.

Each view that shows operator properties now carries its own outlet, marked
`texeraCodeEditorHost` on an `<ng-template>`: the directive attaches the outlet
to CodeEditorService when the view's template is built and detaches it -- only
its own -- when the view is destroyed, which also closes an editor still open
in it. The views are sibling routes, so there is one host at a time. The
button opens into whichever host is up and guards the case of none (every view
that mounts a codearea hosts the editor now, so that is a guard rather than a
state the button shows): a co-editor's open reaching a codearea with no host
neither throws nor marks that operator's editor open, and reaches only the
codearea on that operator. The editor is told its operator by its opener
instead of reading the highlight in its constructor, and may be dragged within
the Form View's page as well as the canvas.

Deliberately not in this change: exposing a code property as a form input. The
form's rules keep the code editor out of what an author may expose
(NON_FORM_FIELD_TYPES) and fall back from it on an old workflow that carries
one (CANVAS_ONLY_FORMLY_TYPES); editing code is not filling in a value, and a
form reader should not be writing code. The step panel is the author's own
editing surface, which is what this fixes.

Closes apache#8760

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FVvP3ttj22f9LB4p9u2anY
@yangzhang75
yangzhang75 force-pushed the fix/form-code-editor-host branch from d1b230a to 1968778 Compare October 19, 2026 07:09
@yangzhang75
yangzhang75 marked this pull request as ready for review October 19, 2026 07:18
@codecov-commenter

Copy link
Copy Markdown

⚠️ JUnit XML file not found

The CLI was unable to find any JUnit XML files to upload.
For more help, visit our troubleshooting guide.

@mengw15
mengw15 requested a balanced review from Copilot October 19, 2026 22:22
@mengw15 mengw15 removed release/v1.2 back porting to release/v1.2 release/v1.3 back porting to release/v1.3 labels Oct 19, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

6 open issues
What changed in this PR

Fixes Form View’s inability to open the Monaco code editor by introducing a view-scoped “code editor host” outlet that is attached/detached with the active route.

Changes:

  • Replace the single, long-lived ViewContainerRef outlet with an attachable/detachable host API in CodeEditorService.
  • Add texeraCodeEditorHost via a new directive and mount it in both Workspace (canvas) and Workflow Form views.
  • Update code editor opening/targeting behavior (operator passed via input) and adjust read-only/inspection UI behavior.
File Description
frontend/​src/​app/​workspace/​service/​code-editor/​code-editor.service.ts Replaces global vc with attach/detach host outlet tracking.
frontend/​src/​app/​workspace/​service/​code-editor/​code-editor.service.spec.ts Adds unit tests for host attach/detach semantics.
frontend/​src/​app/​workspace/​component/​workspace.component.ts Removes old @ViewChild outlet wiring; registers host directive instead.
frontend/​src/​app/​workspace/​component/​workspace.component.spec.ts Updates workspace tests to validate attach/detach interactions.
frontend/​src/​app/​workspace/​component/​workspace.component.html Switches canvas outlet to <ng-template texeraCodeEditorHost>.
frontend/​src/​app/​workspace/​component/​workflow-form/​workflow-form.spec-harness.ts Extends test harness stub for form workflows.
frontend/​src/​app/​workspace/​component/​workflow-form/​workflow-form.rendered.spec.ts Adds rendered test asserting host lifecycle in Form View.
frontend/​src/​app/​workspace/​component/​workflow-form/​workflow-form.component.ts Imports the host directive for Form View.
frontend/​src/​app/​workspace/​component/​workflow-form/​workflow-form.component.scss Raises editor z-index for Form View via deep selector override.
frontend/​src/​app/​workspace/​component/​workflow-form/​workflow-form.component.html Adds Form View editor host outlet template.
frontend/​src/​app/​workspace/​component/​property-editor/​operator-property-edit-frame/​operator-property-edit-frame.component.ts Marks codearea fields as “inspect” when frame isn’t acting as editor.
frontend/​src/​app/​workspace/​component/​property-editor/​operator-property-edit-frame/​operator-property-edit-frame.component.spec.ts Tests inspect mark behavior for code fields.
frontend/​src/​app/​workspace/​component/​codearea-custom-template/​codearea-custom-template.component.ts Opens editor into current host, passes operator id via input, refines coeditor logic.
frontend/​src/​app/​workspace/​component/​codearea-custom-template/​codearea-custom-template.component.spec.ts Adds coverage for button label states, host guard behavior, and operator-targeted opening.
frontend/​src/​app/​workspace/​component/​codearea-custom-template/​codearea-custom-template.component.html Updates label/readonly styling logic to respect inspection mode.
frontend/​src/​app/​workspace/​component/​code-editor-dialog/​code-editor.component.ts Makes operator id an @Input, moves init logic to ngOnInit, and disables AI actions in read-only.
frontend/​src/​app/​workspace/​component/​code-editor-dialog/​code-editor.component.spec.ts Updates construction to set currentOperatorId input; adds tests for new semantics and read-only AI actions.
frontend/​src/​app/​workspace/​component/​code-editor-dialog/​code-editor.component.html Expands drag boundary to work in both Workspace and Workflow Form roots.
frontend/​src/​app/​workspace/​component/​code-editor-dialog/​code-editor.component.browser.spec.ts Aligns browser tests with input-driven operator selection and shared-text staging.
frontend/​src/​app/​workspace/​component/​code-editor-dialog/​code-editor-host.directive.ts Introduces directive that attaches/detaches the active view’s editor outlet.
frontend/​src/​app/​workspace/​component/​code-editor-dialog/​code-editor-host.directive.spec.ts Adds directive lifecycle tests for attachment/detachment/teardown behavior.

Review effort: Lite

Comment on lines +26 to +39
@Component({
template: "<ng-template texeraCodeEditorHost></ng-template>",
imports: [CodeEditorHostDirective],
})
class HostingViewComponent {}

/** Stands in for the editor: what the host holds, and what must be torn down with the view. */
@Component({ template: "editor" })
class HostedComponent implements OnDestroy {
static destroyed = 0;
ngOnDestroy(): void {
HostedComponent.destroyed++;
}
}

beforeEach(async () => {
HostedComponent.destroyed = 0;
await TestBed.configureTestingModule({ imports: [HostingViewComponent, HostedComponent] }).compileComponents();
* The operator whose code this edits, given by the opener: the operator property panel's button
* reads the highlighted operator, the Form View's the operator its field is bound to.
*/
@Input() currentOperatorId!: string;
Comment on lines +161 to 163
ngOnInit(): void {
// Everything here keys on currentOperatorId, an input set after construction.
const operatorType = this.workflowActionService.getTexeraGraph().getOperator(this.currentOperatorId).operatorType;
Comment on lines +1112 to +1114
:host ::ng-deep #code-editor-container.box {
z-index: 21;
}
Comment on lines +36 to +42
export class CodeEditorHostDirective implements OnDestroy {
constructor(
private readonly outlet: ViewContainerRef,
private readonly codeEditorService: CodeEditorService
) {
this.codeEditorService.attachHost(this.outlet);
}

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix frontend Changes related to the frontend GUI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(workflow-form): the code editor button opens nothing in the Form View, which has no editor host

5 participants