Skip to content

feat(FR-3468): add per-field revert-to-loaded-revision icon in DeploymentAddRevisionModal - #9520

Draft
yomybaby wants to merge 3 commits into
mainfrom
feat/FR-3468-add-revision-per-field-reset
Draft

feat(FR-3468): add per-field revert-to-loaded-revision icon in DeploymentAddRevisionModal#9520
yomybaby wants to merge 3 commits into
mainfrom
feat/FR-3468-add-revision-per-field-reset

Conversation

@yomybaby

@yomybaby yomybaby commented Sep 7, 2026

Copy link
Copy Markdown
Member

Resolves #8587 (FR-3468)

What changed

The Custom form of DeploymentAddRevisionModal could only be reset wholesale (resetFields), so undoing one accidental edit meant losing every other edit made alongside it.

This PR snapshots the values the Custom form was last loaded with from a revision — the deployment's current revision via Load current revision, or the source revision the modal was opened from — and renders a small ghost Undo2 icon next to each field whose value has since diverged from that snapshot. Clicking it reverts only that field.

  • The icon is dirty-field-only: hidden while the field still matches the loaded revision, and never rendered at all on a form that was never loaded from a revision.
  • Whole-form reset is unchanged (out of scope, per the issue).
  • Preset mode has no baseline: switching Custom → Preset discards the Custom edits, and the baseline is cleared with them.

Design decisions

  • Baseline is the derived form values, not the raw GraphQL revision. applyRevisionToCustomForm applies non-round-trippable transforms (resource slots → cpu/mem/accelerator, shmem units, health-check enable handling). Comparing against the raw revision would light the icon up on fields the user never touched, so the snapshot is taken from the object that is actually pushed into the form.
  • Field scope = the fields this modal renders itself: model folder, mount destination, subpath, runtime variant, model definition path. The shared form-item components (ResourceAllocationFormItems, ImageEnvironmentSelectFormItems, ModelServiceHealthCheckFormItems, …) own their own layout and are used by other screens, so they are left untouched. The issue explicitly leaves the exact list to implementation.
  • Placement follows the modal's existing idiom — control + IconButton in a BAIFlex row, the same shape the model-folder selector already uses — rather than injecting a button into the <label>.
  • Visibility subscribes via Form.useWatch, not a dependencies render prop. The form engine notifies dependency watchers on user input but not on a programmatic setFieldValue, so a dependencies-driven button would have survived its own click (caught by the test below before it shipped).
  • An empty string and undefined are normalized to the same thing for the comparison: clearing a text input that the revision did not set is not a divergence.

Tests

  • react/src/components/DeploymentAddRevisionModal.test.tsx — two new specs: no revert affordance is rendered while every field still matches the loaded revision, and editing one field then clicking its revert icon restores exactly that value and withdraws the icon.
  • pnpm --filter backend-ai-webui-react exec vitest run src/components/DeploymentAddRevisionModal.test.tsx5 passed (5).
  • make i18n for the new key across the locale files.

Verification

bash scripts/verify.sh (Relay, Lint, Format, TypeScript, agent mappings, terminology):

=== ALL PASS ===

Review notes

  • Open a deployment with a current revision → Add revisionLoad current revision (Custom mode). No revert icons should be visible immediately after loading.
  • Change the Model Mount Destination (or Runtime Variant / Model Folder / Subpath / Model Definition Path): a small undo icon appears next to that field only. Click it — that field returns to the loaded value and the icon disappears; other edits are untouched.
  • Worth a designer's eye: whether the five covered fields are the right set, and whether the icon reads well sitting to the right of each control.

Checklist: (if applicable)

  • Documentation
  • Minium required manager version
  • Specific setting for review (eg., KB link, endpoint or how to setup)
  • Minimum requirements to check during review
  • Test case(s) to demonstrate the difference of before/after

https://claude.ai/code/session_011RFmSEBxxxvCXyquSPtqVJ

…mentAddRevisionModal

The Custom form of the Add-revision modal could only be reset wholesale
(`resetFields`), so undoing one accidental edit meant losing every other
edit made alongside it.

Snapshot the values the Custom form was last loaded with from a revision
(the deployment's current revision via "Load current revision", or the
source revision the modal was opened from) and render a small ghost Undo2
icon next to each field whose value has since diverged from that
snapshot. Clicking it reverts only that field. The icon is hidden while
the field still matches, and never appears at all on a form that was
never loaded from a revision.

The baseline is taken from the DERIVED form values that
`applyRevisionToCustomForm` writes, not from the raw GraphQL revision:
the prefill applies non-round-trippable transforms (resource slots,
shmem units, health-check enable handling), so comparing against the raw
revision would light up fields the user never touched.

Scope is the fields this modal renders itself — model folder, mount
destination, subpath, runtime variant and model definition path. The
shared form-item components (resource allocation, image environment,
health check) own their own layout and are left alone. Whole-form reset
is unchanged, and Preset mode has no baseline (switching to it discards
the Custom edits).

The visibility check subscribes with `Form.useWatch` rather than a
`dependencies` render prop: the engine notifies dependency watchers on
user input but not on a programmatic `setFieldValue`, so a
`dependencies`-driven button would have survived its own click.

Claude-Session: https://claude.ai/code/session_011RFmSEBxxxvCXyquSPtqVJ
@github-actions github-actions Bot added area:ux UI / UX issue. area:i18n Localization size:L 100~500 LoC labels Sep 7, 2026
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for react-coverage (./react)

Status Category Percentage Covered / Total
🔵 Lines 15.8% 5756 / 36422
🔵 Statements 13.28% 7027 / 52905
🔵 Functions 13.98% 907 / 6485
🔵 Branches 9.73% 4923 / 50594
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
react/src/components/DeploymentAddRevisionModal.tsx 40.14% 31.37% 37.25% 42.88% 224-273, 336, 337, 371-384, 402-414, 430-435, 449-451, 797, 816-824, 865-906, 953-1042, 1046-1099, 1311-1312, 1320-1321, 1331, 1333-1334, 1348, 1350-1366, 1375-1379, 1402, 1409-1410, 1421-1436, 1445-1453, 1466-1796, 1815-1867, 1956-1963, 1986-2100, 2165-2188, 2253-2276, 2355-2357, 2436-2565, 2596-2599, 2622-2633, 2740-2833
Generated in workflow #764 for commit 9db365f by the Vitest Coverage Report Action

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

Reverting modelFolderId can leave an invalid stale modelSubpath.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds per-field controls for reverting selected Custom form fields to their loaded revision values.

Changes:

  • Tracks revision baselines and conditionally displays revert buttons.
  • Covers five modal-owned fields with focused tests.
  • Adds localized tooltip keys across supported locales.
File summaries
File Description
resources/i18n/zh-TW.json Registers the revert tooltip key.
resources/i18n/zh-CN.json Registers the revert tooltip key.
resources/i18n/vi.json Registers the revert tooltip key.
resources/i18n/tr.json Registers the revert tooltip key.
resources/i18n/ru.json Registers the revert tooltip key.
resources/i18n/pt.json Registers the revert tooltip key.
resources/i18n/pt-BR.json Registers the revert tooltip key.
resources/i18n/pl.json Registers the revert tooltip key.
resources/i18n/ms.json Registers the revert tooltip key.
resources/i18n/mn.json Registers the revert tooltip key.
resources/i18n/ko.json Adds Korean tooltip text.
resources/i18n/ja.json Registers the revert tooltip key.
resources/i18n/it.json Registers the revert tooltip key.
resources/i18n/id.json Registers the revert tooltip key.
resources/i18n/fr.json Registers the revert tooltip key.
resources/i18n/fi.json Registers the revert tooltip key.
resources/i18n/es.json Registers the revert tooltip key.
resources/i18n/en.json Adds English tooltip text.
resources/i18n/el.json Registers the revert tooltip key.
resources/i18n/de.json Registers the revert tooltip key.
react/src/components/DeploymentAddRevisionModal.tsx Implements baseline tracking and per-field reverts; folder reversion must also clear a stale subpath.
react/src/components/DeploymentAddRevisionModal.test.tsx Tests revert visibility and single-field restoration.
Review details
  • Files reviewed: 22/22 changed files
  • Comments generated: 1
  • Review effort level: Balanced

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

Comment thread react/src/components/DeploymentAddRevisionModal.tsx Outdated
Reverting `modelFolderId` through the per-field revert button calls
`setFieldValue`, which bypasses `BAIVFolderSelect.onChange` and its
subpath cleanup, so a subpath picked under the edited folder could be
submitted against the loaded revision's folder. Restore the loaded
revision's own subpath alongside the folder, mirroring the existing
programmatic folder-change cleanup.

Claude-Session: https://claude.ai/code/session_011RFmSEBxxxvCXyquSPtqVJ

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

Accessibility associations and coupled folder/subpath regression coverage must be fixed before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (6)

Previously missed (3) — in code that hasn't changed since the last review.

react/src/components/DeploymentAddRevisionModal.tsx:2466

  • Moving the field name to the nested noStyle item leaves this outer label without a generated htmlFor, so clicking the visible label no longer focuses the mount-destination input. Preserve the field name on the layout item as well, matching the repository's established nested-item pattern.

This issue also appears on line 2483 of the same file.
react/src/components/DeploymentAddRevisionModal.tsx:2518

  • Moving the field name to the nested noStyle item leaves the visible label unassociated with the selector. It also leaves the outer warning container without the runtimeVariantId_help id referenced by the control's aria-describedby; retain the name on this layout item so both associations remain valid.
    react/src/components/DeploymentAddRevisionModal.tsx:2683
  • Moving the field name to the nested noStyle item leaves this outer label without a generated htmlFor and leaves its validation message without the id referenced by the input's aria-describedby. Retain the name on the layout item so the label and validation text stay associated with the input.

react/src/components/DeploymentAddRevisionModal.tsx:2504

  • When the selected folder differs from the loaded revision, this button can restore the old folder's subpath into the new folder. Changing modelFolderId deliberately clears modelSubpath, so the subpath baseline is only valid while the folder itself still matches; hide this revert action until then (and cover this sequence in a regression test).
                  <RevertToRevisionValueButton
                    name="modelSubpath"
                    baseline={revisionBaseline}
                    form={customForm}
                  />

react/src/components/DeploymentAddRevisionModal.tsx:2487

  • Moving the field name to the nested noStyle item leaves this outer label without a generated htmlFor, so clicking the visible label no longer focuses the subpath picker. Preserve the field name on the layout item as well, matching the repository's established nested-item pattern.
              <BAIFormItem
                label={t('modelService.Subpath')}
                tooltip={t('modelService.SubpathTooltip')}
                style={{ flex: 1 }}
              >

react/src/components/DeploymentAddRevisionModal.tsx:324

  • This new user-facing tooltip resolves to the literal __NOT_TRANSLATED__ in every managed locale except English and Korean because those locale files contain a real value rather than a missing key, so the English fallback is not used. Provide translations for the other 18 locale entries before release.
      label={t('deployment.RevertToLoadedRevisionValue')}
      tooltip={t('deployment.RevertToLoadedRevisionValue')}
  • Files reviewed: 22/22 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread react/src/components/DeploymentAddRevisionModal.tsx
…he tooltip

Copilot pass 2:

- The subpath's own revert would restore the loaded revision's path into a
  folder it does not belong to whenever the folder itself has diverged, so it
  is withheld until the folder matches the loaded revision again; the folder's
  revert restores the pair together.
- Regression spec covers the coupled sequence: pick another folder (its
  onChange clears the subpath), confirm only the folder offers a revert, then
  revert it and assert both baseline values come back.
- `make i18n` seeded `deployment.RevertToLoadedRevisionValue` as
  `__NOT_TRANSLATED__` in the 18 non-en/ko locales, which is what users of
  those locales would have seen in the tooltip. Translated, following the
  `LoadCurrentRevision` idiom that keeps "Revision" as the term.

Claude-Session: https://claude.ai/code/session_011RFmSEBxxxvCXyquSPtqVJ
@yomybaby

yomybaby commented Sep 7, 2026

Copy link
Copy Markdown
Member Author

Copilot pass 2 left three findings in the review body only ("suppressed comments"), with no thread to answer. Recording what happened to each:

Acted on

  • Subpath revert can restore the loaded path into a different folder (line 2504) — fixed in 9db365f1db: the subpath's revert is withheld while modelFolderId itself diverges from the loaded revision, and the folder's revert restores the pair together. Covered by the new reverts the model folder and its subpath as a pair spec.
  • New tooltip resolves to __NOT_TRANSLATED__ outside en/ko — fixed in 9db365f1db: deployment.RevertToLoadedRevisionValue is translated in the 18 remaining extracted locales, following the LoadCurrentRevision idiom that keeps "Revision" as the term. (th is not in i18n.config.js's lngs, so it has no key — unchanged.)

Left open for a human decision

  • Moving the field name onto the nested noStyle item drops the outer label's htmlFor and the <field>_help id its aria-describedby points at (lines 2466 / 2483 / 2487 / 2518 / 2683). The observation is right, but the suggested remedy — keeping name on the layout item as well — is not this codebase's pattern: the pre-existing Model Folder block a few lines above (untouched by this PR) is already outer-layout-item-without-name + nested noStyle-with-name, and putting the name on both would register the field twice. A real fix is either an explicit htmlFor on each layout item (and verifying the Astryx controls forward the generated id) or teaching form-engine/FormItem to derive htmlFor/help ids from its single noStyle child — the latter is a shared-component change beyond this PR's scope. Flagging rather than deciding.

verify.sh: === ALL PASS ===; DeploymentAddRevisionModal.test.tsx 6 passed.

https://claude.ai/code/session_011RFmSEBxxxvCXyquSPtqVJ

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

Labels

area:i18n Localization area:ux UI / UX issue. size:L 100~500 LoC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add per-field reset-to-current-revision icon in DeploymentAddRevisionModal

2 participants