Skip to content

fix(workflow-form): let a card change its operator's siblings the way the panel does, so the HuggingFace task change persists on the form - #8839

Open
yangzhang75 wants to merge 2 commits into
apache:mainfrom
yangzhang75:fix/form-huggingface-task-persists
Open

yangzhang75 wants to merge 2 commits into
apache:mainfrom
yangzhang75:fix/form-huggingface-task-persists

Conversation

@yangzhang75

@yangzhang75 yangzhang75 commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

What changes were proposed in this PR?

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

An exposed HuggingFace model picker (modelId) on the Form View offered a live task selector that could not persist. The card wrote back only the property it was bound to, under the binding's id, and the operator's other properties were a read-only copy with no controls behind them. Changing the task on the form switched the model list, but the operator kept its task; a reload showed the old one, and a model picked meanwhile was saved for a task the operator does not run.

The Form View is another way to show the canvas, so the fix makes the card behave like the operator property panel for the property it exposes, rather than freezing the selector:

  • The field keeps the property's own key (modelId, not the binding id), so a widget that reaches itself or a sibling by key finds the same keys as on the panel. The card's model is the operator's properties, as the panel's is.
  • The operator's other properties ride along as plain controls on the card's form (formly builds only the exposed field), so a widget finds its siblings on the card's form exactly as on the panel's -- the HuggingFace picker writes task through its control, which is also what announces the change, and resets the task-scoped fields through theirs; a control-level write lands in the model.
  • The card writes back what it changed since it was built, in one edit (FormBindingService.writeProperties): the bound property and whatever siblings the widget changed with it, the way the panel writes its whole model. The comparison is against the card's own baseline, a clone of what it was built from, and the baseline is advanced by each write -- not against the operator as it is now, which would have sent back a sibling this card never touched but the canvas, a co-editor or another card on the same operator had changed in the meantime, putting the older value on top of the newer one (Copilot). The guard against formly's blank build-time default stays, for the bound property only.
  • On the form a task change now does on the operator what it does on the canvas (new task, model and task-scoped fields reset), and the reader then picks a model for that task. An empty string and an absent property count as the same value in the write-back, so formly's blank for a property the operator never had is not written on every build (it put an empty property on the operator that the canvas's own form had left out).
  • A held rebuild is also flushed by a click or a key, not only by the focus leaving: closing an nz-select leaves the focus in its search box, so no focusout fires, and a rebuild held while the list was open would have waited for an unrelated event (Copilot). The same deferred re-check decides it, so a filter still being typed is not thrown away. The click is listened for on the document rather than on this page: a dropdown's options are drawn in the CDK's overlay container, a sibling of the app, so the click that picks one -- or that picks the option already chosen, which changes no value and moves no focus -- never bubbles through the page (Copilot). The handler returns at once unless a rebuild is held. Keys stay on the page, where what is typed goes.
  • Nothing that comes back for a task the widget has since left is shown: a model list or a failure (cached for the next asker instead), a poll waiting on another instance's request (stopped by the next load, whichever path that load takes), and a server-side search (the task change forgets the search -- the text, the results, the spinner it was waiting on -- and drops the search subscription with it, which discards both a query still sitting in the 300 ms debounce and a request already out; switchMap drops only a search the next search replaces, and a task change is not a search, so without that a typed query went out moments later under the task the widget had moved to, and its answer filtered that task's list under an empty search box (Copilot)): the fast paths return before the request still out is cancelled, so a slow first fetch could land after a cached task was already shown and paint its models under the other task's name (Copilot; also reachable on main by picking a second task while the first loads).
  • Two more things the two views needed to agree on, found with both open on one workflow: the form held every rebuild while a closed dropdown kept the focus (nz-select leaves the focus on its search box after a pick, and the typing guard counted that as typing), so a change made on the canvas never reached the form until the reader clicked elsewhere -- a dropdown is typing only while its list is open; and the HuggingFace selector on the canvas kept showing the task it was built with when the form (or a co-editor) changed it -- it now follows its field's model on every change detection, as a local pick is followed, and reloads the models (the panel hides the schema's task field, so there is no control to listen to).

Any related issues, documentation, discussions?

Closes #8762. Raised by Copilot on #8437; filed from #8439. A first version froze the task on the form; dropped on the principle that the form stays consistent with the canvas, filling gaps rather than hiding controls. Known limitation, not this PR's: a card's context is read once per build, so between a co-editor's edit of the same operator on the canvas and the form's next rebuild the card can show a stale sibling (every seeded card today; tracked as a follow-up).

How was this PR tested?

Unit tests (vitest). The form's spec: the field is keyed by its property; the card's model carries the operator's other properties and its form carries them as controls; a sibling a widget changed is written back along with the bound value, in one call; only what the card changed is sent, so a sibling someone else changed in the meantime is left alone, and the baseline is advanced so a second edit sends only the second change; a sibling control's change lands in the model; an unchanged sibling emission writes nothing; a held rebuild is flushed by a click as well as by the focus leaving, and not while the list is still open (the host bindings exercised through real events in the rendered spec, including a click on an option inside a .cdk-overlay-container built outside the page, which the test first asserts the page does not contain); the existing write-back guards (unchanged value, blank before an edit, dirtied value, snapshot refresh) hold with the property key. The binding service's spec: writeProperties writes several properties over the current ones and is a no-op for a missing operator. The HuggingFace spec: on a card shaped as the form shapes it (bound field modelId, siblings as plain controls), a task change writes the task to the card's form, resets the model and the task-scoped controls, and the form hears it, which is what the card's write-back listens to; a task change arriving on the model shows the new task and loads its models, while the task already shown or a blank does nothing; a response or a failure that comes back for a task the widget has left does not replace the list it now shows; a task change cancels a search that is still out rather than leaving its spinner on or judging its answer later, drops a query still in the debounce (which then never goes out) and still allows a fresh search under the task now shown; and a poll started for an earlier task is stopped by the next load. The form's spec also pins that a selection made while a rebuild is held is written rather than lost to the rebuild, and that a card's value is compared under the property it is bound to rather than under the binding's id (Copilot). The form's spec also pins the typing guard (a closed dropdown's focused search box is not typing, an open one's is) and that formly's blank for a property the operator never had writes nothing. Each guard was deletion-checked and turned a named test red. eslint, prettier and the production (AOT) build pass; the full frontend suite passes; every line this PR adds is covered.

Checked by hand on a flag-on instance (main-demo, wid 14): on the form, switching the task to Text Generation reloads the model list; after the save the canvas panel shows the new task with the model cleared; picking a model on the form lands on the canvas; before the fix the same switch reverted on reload and the model was saved under the old task.

Video

Before

Screen.Recording.2026-10-03.at.11.14.45.PM.mov

After

Screen.Recording.2026-10-03.at.11.15.09.PM.mov

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

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

🤖 Generated with Claude Code

@github-actions github-actions Bot added fix frontend Changes related to the frontend GUI labels Oct 4, 2026
@Yicong-Huang Yicong-Huang added the release/v1.3 back porting to release/v1.3 label Oct 4, 2026
@github-actions
github-actions Bot requested a review from mengw15 October 4, 2026 04:15
@github-actions

github-actions Bot commented Oct 4, 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 Not labeled automatically — none of the files this PR modifies exist on this branch (frontend/src/app/workspace/component/hugging-face/hugging-face.component.spec.ts, frontend/src/app/workspace/component/hugging-face/hugging-face.component.ts, frontend/src/app/workspace/component/workflow-form/workflow-form.component.spec.ts, frontend/src/app/workspace/component/workflow-form/workflow-form.component.ts, frontend/src/app/workspace/component/workflow-form/workflow-form.rendered.spec.ts, frontend/src/app/workspace/component/workflow-form/workflow-form.spec-harness.ts, frontend/src/app/workspace/service/form-binding/form-binding.service.spec.ts, frontend/src/app/workspace/service/form-binding/form-binding.service.ts). The fix may target code that isn't on this release, or the files were moved/renamed after the branch was cut. Please check and add release/v1.2 by hand if this fix should be backported here — its release manager then has to approve before this PR can merge.

Auto-label run.

@github-actions

github-actions Bot commented Oct 4, 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: @PG1204
    You can notify them by mentioning @PG1204 in a comment.

@codecov-commenter

codecov-commenter commented Oct 4, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.33333% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 92.50%. Comparing base (3726dd7) to head (f4ea02e).
⚠️ Report is 140 commits behind head on main.

Files with missing lines Patch % Lines
...component/workflow-form/workflow-form.component.ts 97.14% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #8839      +/-   ##
============================================
+ Coverage     92.33%   92.50%   +0.17%     
  Complexity     5045     5045              
============================================
  Files          1260     1280      +20     
  Lines         54067    57120    +3053     
  Branches       6755     7514     +759     
============================================
+ Hits          49921    52840    +2919     
- Misses         2486     2518      +32     
- Partials       1660     1762     +102     
Flag Coverage Δ *Carryforward flag
access-control-service 77.38% <ø> (ø) Carriedforward from 0e2517a
agent-service 99.16% <ø> (ø) Carriedforward from 0e2517a
amber 87.31% <ø> (ø) Carriedforward from 0e2517a
computing-unit-managing-service 60.48% <ø> (ø) Carriedforward from 0e2517a
config-service 87.37% <ø> (ø) Carriedforward from 0e2517a
file-service 82.00% <ø> (ø) Carriedforward from 0e2517a
frontend 96.44% <98.33%> (-0.11%) ⬇️
notebook-migration-service 83.73% <ø> (ø) Carriedforward from 0e2517a
pyamber 98.57% <ø> (ø) Carriedforward from 0e2517a
workflow-compiling-service 72.43% <ø> (ø) Carriedforward from 0e2517a

*This pull request uses carry forward flags. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@yangzhang75
yangzhang75 force-pushed the fix/form-huggingface-task-persists branch from 0fa86cf to 342d021 Compare October 4, 2026 04:45
@yangzhang75
yangzhang75 marked this pull request as ready for review October 4, 2026 05:34
@yangzhang75
yangzhang75 marked this pull request as draft October 4, 2026 05:53
@yangzhang75
yangzhang75 force-pushed the fix/form-huggingface-task-persists branch 2 times, most recently from bb770ac to 5b7424e Compare October 4, 2026 06:11
@yangzhang75
yangzhang75 marked this pull request as ready for review October 4, 2026 06:17
@mengw15
mengw15 requested a balanced review from Copilot October 6, 2026 15:12

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.

Copilot review overview

🟡 Changes recommended

Stale card state can overwrite newer edits, while deferred dropdown rebuilds and model-loading races can leave the UI inconsistent.

Review effort: Balanced
Findings: 1 High severity · 2 Medium severity

Open (3)
What changed in this PR

This PR aligns Form View widgets with the canvas property panel, allowing HuggingFace task changes and related property resets to persist.

Changes:

  • Adds atomic multi-property write-back and sibling controls.
  • Synchronizes HuggingFace task/model state across views.
  • Refines dropdown typing detection and expands regression tests.
File Description
form-binding.service.ts Adds merged multi-property writes.
form-binding.service.spec.ts Tests multi-property writes.
workflow-form.spec-harness.ts Mocks the new write API.
workflow-form.component.ts Adds sibling controls, patching, and dropdown handling.
workflow-form.component.spec.ts Tests persistence and typing behavior.
hugging-face.component.ts Follows externally changed tasks.
hugging-face.component.spec.ts Tests Form View and external synchronization.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@yangzhang75
yangzhang75 force-pushed the fix/form-huggingface-task-persists branch from 5b7424e to 29cec39 Compare October 6, 2026 18:39
@mengw15
mengw15 requested a balanced review from Copilot October 6, 2026 19:06

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.

Copilot review overview

🟡 Changes recommended

Dropdown overlay clicks do not reach the host listener, and stale search responses can still replace models for a newer task.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
Resolved since last review (3)

@yangzhang75
yangzhang75 force-pushed the fix/form-huggingface-task-persists branch from 29cec39 to 695e659 Compare October 6, 2026 23:12
@mengw15
mengw15 requested a balanced review from Copilot October 7, 2026 14:31

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.

Copilot review overview

🟡 Changes recommended

Overlay interactions do not reach the host listeners, and external task changes can leave stale search state active.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
Resolved since last review (1)

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.

🟡 Changes recommended

Deferred rebuilds can discard pending selections, and queued searches can filter a new task after the search is cleared.

2 open issues
1 resolved since last review

Review effort: Balanced

@yangzhang75
yangzhang75 force-pushed the fix/form-huggingface-task-persists branch from af2b405 to 0c0e9fc Compare October 18, 2026 23:58
@mengw15
mengw15 requested a balanced review from Copilot October 19, 2026 00:01

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.

🟡 Changes recommended

Dropdown-overlay clicks bypass the host listener and can leave deferred rebuilds pending.

1 open issue
2 resolved since last review

Review effort: Balanced

… the panel does, so the HuggingFace task change persists on the form

An exposed HuggingFace model picker on the Form View offered a task
selector that could not persist: the card wrote back only the property
it was bound to, under the binding's id, and the operator's other
properties were a read-only copy with no controls. Changing the task on
the form switched the model list but saved nothing, so a reader could
pick a model for a task the operator does not run.

The Form View is another way to show the canvas, so a card now behaves
like the operator property panel for the property it exposes. The field
keeps the property's own key; the operator's other properties ride
along as plain controls on the card's form and in its model, so a
widget that reads or writes a sibling by key (the HuggingFace picker
writes task and resets the task-scoped fields) finds the same controls
as on the panel; and the card writes back everything that differs from
the operator in one edit (FormBindingService.writeProperties), the
bound property and the siblings a widget changed. An empty string and
an absent property count as the same value, so formly's blank for a
property the operator never had is not written on every build.

Two more things the two views needed to agree, found with both open on
one workflow: the form held every rebuild while a closed dropdown kept
the focus (its search box counted as typing), so a change made on the
canvas never showed until the reader clicked elsewhere -- a dropdown is
typing only while its list is open; and the HuggingFace selector on the
canvas kept showing the task it was built with when the form (or a
co-editor) changed it -- it now follows its field's model on every
check, as a local pick is followed (the panel hides the schema's task
field, so there is no control to listen to).

Closes apache#8762

Co-Authored-By: Claude Fable 5.1 <[email protected]>
@yangzhang75
yangzhang75 force-pushed the fix/form-huggingface-task-persists branch from 0c0e9fc to 0e2517a Compare October 19, 2026 00:22
@mengw15
mengw15 requested a balanced review from Copilot October 19, 2026 22:23

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

3 open issues
1 resolved since last review

Review effort: Lite

…ild is held, not one per key (review)

Copilot on apache#8839: with a rebuild held, every keyup scheduled its own later() callback. A
flushQueued flag makes the re-check single-flight, cleared when the callback runs so the next
hold gets its own; a spec asserts five interactions queue one and run the held rebuild once,
and that a later hold is re-checked again.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01FVvP3ttj22f9LB4p9u2anY
@mengw15 mengw15 removed the release/v1.3 back porting to release/v1.3 label Nov 9, 2026

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 HuggingFace picker's task selector stays live in the form, so a reader can pick a mismatched task

5 participants