feat(workflow-builder-redesign): person-framed approval control - #9850
Draft
bookwormsuf wants to merge 7 commits into
Draft
feat(workflow-builder-redesign): person-framed approval control#9850bookwormsuf wants to merge 7 commits into
bookwormsuf wants to merge 7 commits into
Conversation
Reword the approval control to tie to the person rather than the step, behind useIsWorkflowBuilderRedesign(). Adds a label for the Yes/No selector (previously placeholder-only) and helper text for the fields list explaining the auto-add. Drops the approvals toggle tooltip and the fields-list tooltip under the flag; the fields-list tooltip key is removed rather than left orphaned. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Approval now reads before the fields it feeds into, matching the intended reading order: decide whether this person approves, pick their decision field, then see it in the fields list below. Applied to both the edit form and the read-only step card so the two views agree. Only the sequence differs between flag states, so each section is built once and reordered rather than duplicating the subtree per branch. Output is byte-identical with the flag off. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Selecting a Yes/No field as the approval field now adds it to the step's edit list automatically, removing the manual double-step the backend already required (approval_field must be in edit). Auto-add is one-way: nothing ever removes an id from edit, not on toggle-off, field-clear, or an A->B switch. The leftover stays as a normal removable chip. This is deliberate — auto-add is a convenience, auto-remove is a decision, and removing silently would destroy a choice the admin may have made by hand. Validation reads edit via getValues rather than a watched closure: QuestionsBlock triggers it synchronously before this component re-renders, so a closed-over value would be one change stale. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Addresses review findings on the auto-assign helper. The old helper was a Set union with one caller, so its interface was as complex as its implementation and the decisions that could actually break lived outside it — the redesign guard in the component, and the one-way invariant only in a comment. Its tests asserted Set semantics, not the rule. It now takes the whole change (current edit, incoming approval value, whether auto-assign applies) and returns the next edit list, so the invariant is testable at the interface. Covers clearing the field and the flag-off case, neither of which the previous shape could express. Also renamed: nothing reconciled, and the old name promised a symmetric API the module deliberately refuses to have. Moved to utils/ alongside isFirstStepByStepNumber, the convention for pure helpers in this tree. Reading edit via getValues inside the handler removes the last watched closure, which was the source of the stale-read bug. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Auto-save-on-switch only saves when formState.isDirty is true. The approval toggle's checked state is React state rather than RHF, so clearing approval_field is the only thing that can mark the form dirty when approval is switched off — without shouldDirty it stayed clean and the change was discarded on card switch. Same for the auto-assign write to `edit`, so a change to the fields list is never silently dropped either. NOTE: these are necessary but not sufficient. EditStepBlock reads formState.isDirty only inside the auto-save effect, never during render, so RHF never subscribes and isDirty is false on a freshly mounted card regardless. That is a pre-existing issue in the click-to-edit feature and is being raised separately. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drops a plan-section reference that resolves to nothing in this repo, and tightens the helper docblock and the two ordering comments. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A step without approval showed an "Approvals" heading and an "Approval not required in this step" badge. The reorder moved that to the top of the card, so every non-approval step now opened with a section saying nothing. Hidden under the flag when the step has no approval field. Steps whose approval field was deleted still render, so the error stays visible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
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.
Updates the workflow builder UI (behind useIsWorkflowBuilderRedesign()) to frame approvals as a person’s action, reorder approval/fields blocks, and auto-add the chosen approval Yes/No field into the step’s edit list.
Changes:
- Reworded i18n copy and added redesign-only helper/labels for approvals + auto-add explanation.
- Reordered approval vs fields sections in both editable and read-only step cards under the redesign flag.
- Added
nextEditFieldsForApprovalutility (+ tests) to implement one-way auto-assign ofapproval_fieldintoedit.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/frontend/src/i18n/locales/features/admin-form/sidebar/workflow/index.ts | Extends workflow locale typings for redesign-only helper/labels. |
| apps/frontend/src/i18n/locales/features/admin-form/sidebar/workflow/en-sg.ts | Updates redesign copy for approvals and adds auto-add helper text. |
| apps/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/utils/nextEditFieldsForApproval.ts | Introduces one-way helper to add approval field into edit. |
| apps/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/utils/nextEditFieldsForApproval.test.ts | Adds unit tests for the one-way auto-assign helper. |
| apps/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/InactiveStepBlock/InactiveStepBlock.tsx | Reorders approvals/fields display in collapsed card under flag; hides empty approvals in redesign. |
| apps/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/QuestionsBlock.tsx | Adds redesign helper text + triggers approval validation on edit changes. |
| apps/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/EditStepBlock.tsx | Swaps Approvals/Questions section order under flag without duplicating subtrees. |
| apps/frontend/src/features/admin-form/create/workflow/components/WorkflowContent/EditStepBlock/ApprovalsBlock.tsx | Person-framed toggle copy, redesign-only selector label, and auto-add approval field into edit. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+162
to
+174
| render={({ field: { value = '', onChange, ...rest } }) => { | ||
| const handleApprovalFieldChange = (newValue: string) => { | ||
| onChange(newValue) | ||
| setValue( | ||
| FIELDS_TO_EDIT_NAME, | ||
| nextEditFieldsForApproval({ | ||
| edit: getValues(FIELDS_TO_EDIT_NAME), | ||
| approvalFieldId: newValue, | ||
| isEnabled: isRedesign, | ||
| }), | ||
| { shouldDirty: true }, | ||
| ) | ||
| } |
| const handleFieldsChange = (newValue: string[]) => { | ||
| onChange(newValue) | ||
| if (isRedesign && selectedApprovalField) { | ||
| trigger(APPROVAL_FIELD_NAME) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The approval control reads as a property of the step, not a job a person does. We want to tie the action of approving closer to the person involved in the step.
Solution
The toggle is reworded to tie to the person, the approval block moves above the fields it feeds into, and picking a Yes/No approval field now adds it to the fields list automatically. Storage is unchanged:
approval_field(a Yes/No field id) plusedit(an array of field ids), exactly as today.Reading order now follows the decision: decide whether this person approves, pick their decision field, then see that field in the fields list below. The read-only step card mirrors the same order so a saved step reads the same whether it's open or collapsed.
Auto-assign is deliberately one-way. Picking a field adds it to
edit; nothing ever removes an id — not on toggle-off, not on clearing the field, not when switching the approval field from A to B. Adding is a convenience and is trivially undone. Removing would destroy a field the admin may have chosen by hand, in a part of the form they aren't looking at.Important
Everything is behind
useIsWorkflowBuilderRedesign()— the copy, the block reorder in both components, and the auto-assign behaviour. With the flag off, behaviour is byte-identical to before this PR.Depends on #9849
Reviewers testing locally should merge or check out #9849 first. Without it, auto-save-on-card-switch discards edits to any already-saved step, so approval changes will appear to vanish for reasons unrelated to this PR. Two
setValue(..., { shouldDirty: true })calls inApprovalsBlockare inert until that lands.Alternatives considered
step_typeschema column (FRM-2488). Shelved: it desyncs live production records without unlocking a new capability. Approval already has a home in the optionalapproval_field— presence means the step approves. The real problem was UI framing, not storage.~components/DropdownMultiSelect —MultiSelectItem's close button only takes a component-wideisDisabled, andComboboxItemhas no per-itemlockedfield. Additive and low-risk, but it widens the blast radius app-wide for one ticket. The chip stays removable, with the existing "field not assigned to this person" validation as the backstop.Known gaps (not addressed here)
develop(there's no guard there either), so this PR doesn't introduce it — but moving approval above the fields makes it more prominent. Descoped to the empty-states workstream (FRM-2492).nextEditFieldsForApprovalis a shallow seam. It owns the add rule, but the redesign-flag guard and the never-remove invariant it documents are enforced by its caller. Raising it further was reviewed and judged out of scope here.edit" rule lives in three places — the adder, the sibling block's re-validation, and thevalidateclosure. No single module owns it. A hook owning both directions would fix it; larger than this PR.editmay contain this step'sapproval_field, so an earlier respondent could answer the approver's question. Pre-existing and only ever validated within a single step. Accepted as out of scope: this PR reframes one step's control, not cross-step field governance.Screenshots
Breaking Changes
No — backwards compatible. No schema change, no migration, no backend change. Existing saved steps already satisfy
approval_field ∈ editbecause the backend has always enforced it, so they load and validate unchanged. Behind a flag; revert is a straight revert.Tests
TC1: person-framed copy and layout (flag ON)
TC2: auto-assign
TC3: one-way rule (intended behaviour, not a bug)
TC4: validation
TC5: flag OFF is unchanged
TC6: edge cases
🤖 Generated with Claude Code