feat(mrf-cutover): default CreateFormModal to MRF with storage-mode escape hatch (4/6)#9465
Conversation
e17eda1 to
a903570
Compare
d7024b6 to
4db88d8
Compare
a903570 to
bbb670a
Compare
…e mode When mrf-cutover is on, the create-form details screen no longer offers a response-mode choice — Multirespondent is the implicit default. An "old version of FormSG" link composed from the user's beta flags navigates to a new storage-mode-only screen (title + create + back). Submitting routes through the existing storage-mode mutation. Closes #9453. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Wrap escape-hatch link in an info InlineMessage to match Figma - Storage-mode page header reads "Set up a Storage mode form" - Add storage-mode subtitle explaining that storage mode is outdated Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… spacing in cutover flow The MRF default screen previously showed both the escape-hatch infobox and the existing data-classification infobox, cluttering the layout. The cutover Figma drops the latter and uses 2.5rem rhythm around the escape-hatch infobox and between input/button on the storage-mode page. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4db88d8 to
9965cf9
Compare
kevin9foong
left a comment
There was a problem hiding this comment.
🤖 AI code review
Reviewed git diff origin/develop...HEAD (target develop) across four axes — Standards, Spec, Architecture, Divergent — each verified against false positives. 4 inline comments posted; all non-blocking.
Dropped during verification (for transparency):
- MRF default leaking into the Duplicate Form modal — the
responseMode: Multirespondentinjection lives in the shareduseCommonFormWizardProvider, but that line is already ondevelopand isn't part of this PR's diff, so it's out of scope here (the dupe modal is #9454). -1animation-direction hack ingoToStorageModeDetails— acceptable: correctly breadcrumbedTODO [MRF-CUTOVER], and the real slide-animation fix is out of scope for code the cutover deletes.- Escape-hatch copy hardcoded in
escapeHatchCopy.ts— out of this diff (introduced by the merged #9451).
🤖 This review was generated by an AI code review. Please verify before acting on it.
| aria-label="Back" | ||
| icon={<BiArrowBack />} | ||
| onClick={goToMrfDetails} | ||
| mr="0.5rem" | ||
| /> | ||
| Set up a Storage mode form | ||
| </Flex> | ||
| </Container> | ||
| </ModalHeader> | ||
| <ModalBody whiteSpace="pre-wrap"> | ||
| <Container maxW="45rem" p={0}> | ||
| <Text textStyle="body-2" color="secondary.500" mb="2rem"> | ||
| Storage mode is outdated and no longer receives new features. Only |
There was a problem hiding this comment.
issue (non-blocking): new user-facing copy here is hardcoded English instead of going through t().
What's happening: the back aria-label="Back" (line 54), the header Set up a Storage mode form (line 59), and the body text Storage mode is outdated… (line 66) are inline strings — but the same screen already uses t('features.workspace.modals.forms.create…') for the title label and the create button.
Why it bites later: these strings won't appear in the ms / ta / zh locales, and copy now lives in two different conventions within one file.
Suggestion: route all three through t() with new keys, like the sibling screens. SaveSecretKeyContent.tsx even wraps its aria-labels in t().
Confidence: 95 — verified; flagged independently by the Standards, Spec, Architecture and Divergent axes.
🤖 This comment was generated by an AI code review. Please verify before acting on it.
There was a problem hiding this comment.
Ignored for now since we do not wish to support i18n since deprecation message is going to be temporary.
| useCreateFormWizard, | ||
| } from '../CreateFormWizardContext' | ||
|
|
||
| const FORM_TITLE_LENGTH_WARNING = 65 |
There was a problem hiding this comment.
suggestion (non-blocking): extract the duplicated title input rather than copy-pasting it.
What's happening: FORM_TITLE_LENGTH_WARNING = 65 (line 27) and the whole title FormControl block (label, Skeleton, Input, validation, length-warning, ~lines 71–90) are copied near-verbatim from CreateFormDetailsScreen.tsx (constant at line 35, block at lines 91–115).
Why it bites later: the two copies drift — a change to the warning threshold or the validation has to be made in both, and this cutover screen is temporary so divergence is likely.
Suggestion: pull the shared part into a small <FormTitleInput/> (both screens already share useFormTitleValidationRules and one formMethods), or at minimum export the constant.
Confidence: 82 — verified; raised by the Standards and Divergent axes.
🤖 This comment was generated by an AI code review. Please verify before acting on it.
There was a problem hiding this comment.
accepted. clarified with kenneth (pm) that we probably will keep this setup modal in the near/mid term, so ideally want to ensure this component is well implemented without dups. this will also accelerate AI coding in the future.
There was a problem hiding this comment.
fixed, moved it into its own reusable component
| publicKey: keypair.publicKey, | ||
| workspaceId, | ||
| emails: emails.filter(Boolean), | ||
| emails: emails ? emails.filter(Boolean) : cutoverDefaultEmails, |
There was a problem hiding this comment.
suggestion (non-blocking): set the cutover email default at the form's defaultValues, not inside the submit handler.
What's happening: emails: emails ? emails.filter(Boolean) : cutoverDefaultEmails guards a field typed non-optional string[] (CreateFormWizardContext.tsx:26), but the new storage screen never registers emails, so at runtime it is undefined — the type and the runtime disagree. The [user.email] default itself is correct (it matches the legacy EmailFormRecipientsInput default).
Suggestion — pick one:
- seed
defaultValues.emails = user?.email ? [user.email] : []whenisMrfCutoverEnabled, next to the existingresponseModedefault, so the create handler stays honest with its type; or - narrow the type to
emails?: string[].
Confidence: 75 — verified type/runtime mismatch; below the correctness gate's 80 bar, kept as Architecture / Divergent design feedback.
🤖 This comment was generated by an AI code review. Please verify before acting on it.
There was a problem hiding this comment.
emails is indeed set as must be defined - it is a type violation for me to do a check for email definition here. will apply a fix to this and subsequent PRs.
There was a problem hiding this comment.
lets do option 2, following the mental model “We might not have emails in form state; fix it when we send.”
this keeps the same decision made in this PR but resolves the type violation
| features: { [featureFlags.mrfCutover]: { defaultValue: true } }, | ||
| }) | ||
|
|
||
| export const MrfCutoverOn = Template.bind({}) |
There was a problem hiding this comment.
issue (non-blocking): the storage-mode page itself has no Storybook story / Chromatic baseline.
What's happening: the added stories (MrfCutoverOn + the three beta-flag variants) all render only the MRF default screen. None has a play / userEvent step that clicks the escape-hatch link through to CreateFormStorageModeScreen, so that screen is never captured.
Why it matters: issue #9453 asks for stories covering "escape-hatch open / storage-mode page", and without one Chromatic won't baseline the new screen.
Suggestion: add one story with a play function that clicks the escape-hatch link and asserts the storage-mode page renders.
Confidence: 75 — verified unmet acceptance criterion; scored just under the Spec gate's 80 bar, included here as a follow-up.
🤖 This comment was generated by an AI code review. Please verify before acting on it.
There was a problem hiding this comment.
lets skip this for now, it is a good to have, but not strictly necessary.
Code Review: PR #9468 — refactor(mrf-cutover): lift wizard navigation + pass source as prop (2/6)Author: kevin9foong · Base: OverviewScaffolding for the upcoming MRF cutover. Three concrete changes:
The refactor (#1, #2) is clean and the behavioral claim holds: with Strengths
Issues & Suggestions1. Dead code in this PR (confirmed by grep). None of
2. 3. 4. Risk AssessmentLow. The behavior-changing path is feature-flagged off, the context→prop migration preserves the existing VerdictApprove with minor comments. Solid, well-scoped refactor that does what the description says. I'd want answers on (2) i18n intent and a comment addressing (1) the order-of-activation footgun before merge, but neither blocks. The TC1 manual checklist (flag-off modal flows) is still unchecked — confirm that's been run. made with claude's /review skill |
* chore: add agent context and scratch to gitignore (#9544) * Merge pull request #9545 from opengovsg/fix/optional-pdf-display fix: render signature question with empty answer when signature is not captured in PDF * Merge pull request #9465 from opengovsg/feat/create-form-modal-cutover feat(mrf-cutover): default CreateFormModal to MRF with storage-mode escape hatch (4/6) * fix(NumberField): remove +/- stepper buttons (#9444) (#9448) Co-authored-by: dew1997 <109208984+dew1997@users.noreply.github.com> * fix: enable line breaks in thank you page message (#9507) * fix: enable line breaks in thank you page message (#9433) * fix: lint frontend EndPageBlock (#9509) Fix frontend lint error in EndPageBlock Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --------- Co-authored-by: Bhanu Pratap Singh Rathore <bhanur05@gmail.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> * Merge pull request #9527 from opengovsg/fix/signature-hover-theme-color fix(sig): match hover state to form theme color * fix(table): add default for dropdown columns (#9526) fix(table): add default for dropdown columns (#9476) Co-authored-by: Raj Shekar Patha <rajashekarpatha07@gmail.com> * feat(i18n): add landing payments and not found error pages (#9522) * feat(i18n): add landing payments and not found error pages (#9497) feat: add i18n for landing payments and not found error pages * fix(i18n/payments): remove spurious whitespace Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix(payments): add i18n t hook to `useCallback()` --------- Co-authored-by: Samuel Tan <126168312+SAMTAN444@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * chore: bump version to 7.23.0 --------- Co-authored-by: Kevin Foong <55353265+kevin9foong@users.noreply.github.com> Co-authored-by: scottheng96 <44297674+scottheng96@users.noreply.github.com> Co-authored-by: LoneRifle <LoneRifle@users.noreply.github.com> Co-authored-by: dew1997 <109208984+dew1997@users.noreply.github.com> Co-authored-by: Bhanu Pratap Singh Rathore <bhanur05@gmail.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: Raj Shekar Patha <rajashekarpatha07@gmail.com> Co-authored-by: Samuel Tan <126168312+SAMTAN444@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Problem
Per the PRD (issue #9454): the Create Form modal currently defaults to storage mode and offers it side-by-side with MRF, which slows org-wide MRF migration and leaves admins on the legacy v1 webhook schema by default. Closes #9453.
Solution
Under the
mrf-cutoverflag:CreateFormModalto Multirespondent — no mode toggle on the first screen.CreateFormStorageModeScreenreachable via the escape-hatch link ("old version of FormSG"), composed from theescapeHatchCopyfoundation in PR 1. Title input + create button, with a "back" affordance to return to the MRF default screen.undefinedemail crash that surfaced during escape-hatch navigation.When the flag is off, the original modal renders unchanged.
Breaking Changes
No - gated behind
mrf-cutoverfeature flag. BackendcreateFormstill acceptsEncryptwithout flag checks (seedocs/adr/0001-frontend-only-storage-form-gating.md).Tests
TC1: flag on, default MRF flow
mrf-cutoverflag; open Create FormTC2: escape hatch → storage mode
CreateFormStorageModeScreenrenders with title input + create buttonTC3: escape-hatch copy by beta flag
betaFlags.children→ copy mentions payments + children fieldsbetaFlags.createStorageModeForV1Webhook→ copy mentions payments + webhooks v1TC4: flag off
mrf-cutoverflag; open Create Form