Skip to content

fix: make validation messages explain how to fix each field - #698

Merged
thomasluizon merged 3 commits into
redesign/mainfrom
fix/ticket-1204-validation-fix-copy
Oct 4, 2026
Merged

thomasluizon merged 3 commits into
redesign/mainfrom
fix/ticket-1204-validation-fix-copy

Conversation

@thomasluizon

@thomasluizon thomasluizon commented Oct 4, 2026 •

Copy link
Copy Markdown
Owner

Closes thomasluizon/orbit-tickets#1204

The shared validation templates exposed C# property names beside form fields, including English names inside Portuguese messages. Each inventoried field now uses localized instructional copy while retaining its original FluentValidation error code, limits and HTTP response shape. Tag color errors ask the person to choose from the swatches; login and both step-up flows return the existing six-digit instruction for all three code rules.

Common/ErrorCopy.ValidationFields.cs holds the field catalog, and Common/ValidationCopyExtensions.cs attaches its key with WithMessage plus internal validation state. Localization selects that key rather than overwriting the rule with its generic code template. The field key stays internal; errorDetails.code is still the original validation code. Common/ErrorCopy.Validation.cs keeps developer-only generic templates with unquoted names and contains the dedicated validation-code instructions.

The rules attach the copy in Auth, ApiKeys, Habits, Tags, Goals, Support, Referrals, Profile and ChecklistTemplates. Shared habit rules also cover onboarding, bulk creation and suggestions. Sub-habit editing extends the existing lookup for invalid titles to invalid descriptions and emoji, so those fields receive child-specific copy. Valid text avoids that lookup. Domain guards and request DTOs are unchanged.

Inventory

The table shows emitted codes, their fields and the final messages. Maximum-length rows show the actual rule limit and preserve the entered-count placeholder. The two {0} limits are supplied by the existing call sites: 20 linked habits and 5 tags.

Source evidence: UI PR 1573 and the API validators, plus read-only inspection of the consumer's packages/shared/src/i18n/en.json, pt-BR.json, login hooks, API key management, name editing and checklist template callers. There are no consumer edits. The inventory includes the original forms plus referral code, profile name, API key name, checklist text and chat message. Suggestion and bulk endpoints validate the same person-entered habit and tag text.

Code Field and validator flow en pt-BR
NotEmptyValidator Email: SendCode, VerifyCode, Support Enter your email Digite seu email
EmailValidator Email: SendCode, VerifyCode, Support Use a complete email, like name@example.com Use um email completo, como nome@exemplo.com
NotEmptyValidator Code: VerifyCode, ApiKeyCode, DeletionCode Enter all 6 digits Digite os 6 dígitos
ExactLengthValidator Code: VerifyCode, ApiKeyCode, DeletionCode Enter all 6 digits Digite os 6 dígitos
VALIDATION_VERIFICATION_CODE_FORMAT Code: VerifyCode, ApiKeyCode, DeletionCode Enter all 6 digits Digite os 6 dígitos
NotEmptyValidator Title: CreateHabit, UpdateHabit, OnboardingHabit, BulkHabit, SuggestHabitSetup, SuggestTags Give the habit a title Dê um título ao hábito
MaximumLengthValidator Title: CreateHabit, UpdateHabit, OnboardingHabit, BulkHabit, SuggestHabitSetup, SuggestTags Shorten the habit title to 200 characters or fewer. You entered {TotalLength}. Encurte o título do hábito para até 200 caracteres. Você digitou {TotalLength}.
VALIDATION_SUB_HABIT_TITLE_REQUIRED Title: CreateSubHabit, UpdateSubHabit Give the sub-habit a title Dê um título ao sub-hábito
VALIDATION_SUB_HABIT_TITLE_LENGTH Title: CreateSubHabit, UpdateSubHabit Shorten the sub-habit title to 200 characters or fewer Encurte o título do sub-hábito para até 200 caracteres
MaximumLengthValidator Description: CreateHabit, UpdateHabit, OnboardingHabit, SuggestTags Shorten the habit description to 10000 characters or fewer. You entered {TotalLength}. Encurte a descrição do hábito para até 10000 caracteres. Você digitou {TotalLength}.
MaximumLengthValidator Description: CreateSubHabit, UpdateSubHabit Shorten the sub-habit description to 10000 characters or fewer. You entered {TotalLength}. Encurte a descrição do sub-hábito para até 10000 caracteres. Você digitou {TotalLength}.
MaximumLengthValidator Emoji: CreateHabit, UpdateHabit, OnboardingHabit, BulkHabit Shorten the habit emoji to 32 characters or fewer. You entered {TotalLength}. Encurte o emoji do hábito para até 32 caracteres. Você digitou {TotalLength}.
MaximumLengthValidator Emoji: CreateSubHabit, UpdateSubHabit Shorten the sub-habit emoji to 32 characters or fewer. You entered {TotalLength}. Encurte o emoji do sub-hábito para até 32 caracteres. Você digitou {TotalLength}.
GreaterThanValidator FrequencyQuantity: CreateHabit, UpdateHabit, OnboardingHabit, BulkHabit, CreateSubHabit, UpdateSubHabit Set the frequency to 1 or higher Defina a frequência como 1 ou mais
NotEmptyValidator Name: CreateTag, UpdateTag Give the tag a name Dê um nome à tag
MaximumLengthValidator Name: CreateTag, UpdateTag Shorten the tag name to 50 characters or fewer. You entered {TotalLength}. Encurte o nome da tag para até 50 caracteres. Você digitou {TotalLength}.
NotEmptyValidator Color: CreateTag, UpdateTag Choose a color from the swatches Escolha uma cor da paleta
VALIDATION_TAG_COLOR_FORMAT Color: CreateTag, UpdateTag Choose a color from the swatches Escolha uma cor da paleta
NotEmptyValidator Title: CreateGoal, UpdateGoal, OnboardingGoal Give the goal a title Dê um título à meta
MaximumLengthValidator Title: CreateGoal, UpdateGoal, OnboardingGoal Shorten the goal title to 200 characters or fewer. You entered {TotalLength}. Encurte o título da meta para até 200 caracteres. Você digitou {TotalLength}.
MaximumLengthValidator Description: CreateGoal, UpdateGoal, OnboardingGoal Shorten the goal description to 500 characters or fewer. You entered {TotalLength}. Encurte a descrição da meta para até 500 caracteres. Você digitou {TotalLength}.
NotEmptyValidator Unit: CreateGoal, UpdateGoal, OnboardingGoal Set the unit Defina a unidade
MaximumLengthValidator Unit: CreateGoal, UpdateGoal, OnboardingGoal Shorten the goal unit to 50 characters or fewer. You entered {TotalLength}. Encurte a unidade da meta para até 50 caracteres. Você digitou {TotalLength}.
GreaterThanValidator TargetValue: CreateGoal, UpdateGoal, OnboardingGoal Set the target to a value greater than 0 Defina a quantidade da meta como um valor maior que 0
GreaterThanOrEqualValidator NewValue: GoalProgress Enter a value of 0 or higher Informe um valor igual ou maior que 0
MaximumLengthValidator Note: GoalProgress Shorten the progress note to 500 characters or fewer. You entered {TotalLength}. Encurte a nota de progresso para até 500 caracteres. Você digitou {TotalLength}.
NotEmptyValidator Name: Support, ProfileName Enter a name. Digite um nome.
NotEmptyValidator Subject: Support Pick a subject so we can route it properly. Escolha um assunto para a gente encaminhar certo.
MaximumLengthValidator Subject: Support Shorten the subject to 200 characters or fewer. You entered {TotalLength}. Encurte o assunto para até 200 caracteres. Você digitou {TotalLength}.
NotEmptyValidator Message: Support Write at least one sentence about what happened. Escreva pelo menos uma frase sobre o que aconteceu.
MaximumLengthValidator Message: Support Shorten your support message to 5000 characters or fewer. You entered {TotalLength}. Encurte sua mensagem de suporte para até 5000 caracteres. Você digitou {TotalLength}.
NotEmptyValidator ReferralCode: Referral Enter the referral code Digite o código de indicação
MaximumLengthValidator ReferralCode: Referral Shorten the referral code to 50 characters or fewer. You entered {TotalLength}. Encurte o código de indicação para até 50 caracteres. Você digitou {TotalLength}.
VALIDATION_PROFILE_NAME_LENGTH Name: ProfileName Use 50 characters or fewer. Use no máximo 50 caracteres.
VALIDATION_API_KEY_NAME_REQUIRED Name: ApiKey Give the API key a name Dê um nome à chave de API
VALIDATION_API_KEY_NAME_LENGTH Name: ApiKey Shorten the API key name to 50 characters or fewer Encurte o nome da chave de API para até 50 caracteres
NotEmptyValidator Name: Template Give the checklist template a name Dê um nome ao modelo de checklist
MaximumLengthValidator Name: Template Shorten the checklist template name to 100 characters or fewer. You entered {TotalLength}. Encurte o nome do modelo de checklist para até 100 caracteres. Você digitou {TotalLength}.
VALIDATION_TEMPLATE_ITEM_REQUIRED Items[0]: Template Write the checklist item Escreva o item do checklist
VALIDATION_TEMPLATE_ITEM_LENGTH Items[0]: Template Shorten the checklist item to 500 characters or fewer Encurte o item do checklist para até 500 caracteres
VALIDATION_CHAT_MESSAGE_REQUIRED Message: Chat Write a message Escreva uma mensagem
VALIDATION_CHAT_MESSAGE_LENGTH Message: Chat Shorten your message to 4000 characters or fewer Encurte sua mensagem para até 4000 caracteres
MaximumLengthValidator Tags[0]: BulkHabit Shorten the tag name to 50 characters or fewer. You entered {TotalLength}. Encurte o nome da tag para até 50 caracteres. Você digitou {TotalLength}.
VALIDATION_BULK_TAG_LIMIT TagsCount: BulkHabit Select up to 5 tags for this habit Escolha até 5 tags para este hábito
VALIDATION_TEMPLATE_ITEM_LIMIT ItemsCount: Template Add up to 50 checklist items Adicione até 50 itens ao checklist
VALIDATION_FREQUENCY_QUANTITY_REQUIRED FrequencyQuantity: CreateHabit, UpdateHabit, OnboardingHabit, BulkHabit Set how often the habit repeats Defina com que frequência o hábito se repete
VALIDATION_CHECKLIST_ITEM_LENGTH ChecklistItems: Habit checklist Shorten each checklist item to 500 characters or fewer Encurte cada item do checklist para até 500 caracteres
VALIDATION_SUB_HABIT_LIMIT SubHabits: Sub-habit list Add up to 20 sub-habits Adicione até 20 sub-hábitos
VALIDATION_HABIT_TAG_LIMIT TagIds: Tag assignment Select up to 5 tags Escolha até 5 tags
VALIDATION_HABIT_GOAL_LIMIT GoalIds: Habit linked goals Link up to 10 goals to this habit. Vincule até 10 metas a este hábito.
VALIDATION_GOAL_HABIT_LIMIT HabitIds: Goal linked habits Link up to 20 habits to this goal. Vincule até 20 hábitos a esta meta.
MAX_HABITS_PER_GOAL HabitIds: Goal creation Link up to {0} habits to this goal. Vincule até {0} hábitos a esta meta.
MAX_TAGS_PER_HABIT TagIds: Tag assignment Select up to {0} tags for this habit. Escolha até {0} tags para este hábito.

Client keys for follow-up

Only keys that still differ appear here. The existing instructions for code format, email format, goal progress and support subject/message match the API word for word in each applicable locale. pt-BR habit, sub-habit, tag and goal title instructions and goal unit also match; their English client keys still state rules.

Key Client en Client pt-BR API instruction en / pt-BR
habits.form.titleRequired Title is required Dê um título ao hábito Give the habit a title / Dê um título ao hábito
habits.form.titleTooLong Title must be 200 characters or fewer O título pode ter no máximo 200 caracteres Shorten the habit title to 200 characters or fewer. You entered {TotalLength}. / Encurte o título do hábito para até 200 caracteres. Você digitou {TotalLength}.
habits.form.descriptionTooLong Description must be 10,000 characters or fewer A descrição pode ter no máximo 10.000 caracteres Shorten the habit description to 10000 characters or fewer. You entered {TotalLength}. / Encurte a descrição do hábito para até 10000 caracteres. Você digitou {TotalLength}.
habits.form.emojiTooLong Emoji must be 32 characters or fewer O emoji deve ter no máximo 32 caracteres Shorten the habit emoji to 32 characters or fewer. You entered {TotalLength}. / Encurte o emoji do hábito para até 32 caracteres. Você digitou {TotalLength}.
habits.form.frequencyRequired Frequency must be at least 1 A frequência precisa ser pelo menos 1 Set the frequency to 1 or higher / Defina a frequência como 1 ou mais
habits.form.subHabitTitleRequired Sub-habit title is required Dê um título ao sub-hábito Give the sub-habit a title / Dê um título ao sub-hábito
habits.form.subHabitTitleTooLong Sub-habit title must be 200 characters or fewer O título do sub-hábito pode ter no máximo 200 caracteres Shorten the sub-habit title to 200 characters or fewer / Encurte o título do sub-hábito para até 200 caracteres
habits.form.subHabitLimit You can add up to 20 sub-habits Você pode adicionar até 20 sub-hábitos Add up to 20 sub-habits / Adicione até 20 sub-hábitos
habits.form.tagLimit You can select up to 5 tags Você pode escolher até 5 tags Select up to 5 tags / Escolha até 5 tags
habits.form.goalLimit Goal limit reached Você atingiu o limite de metas Link up to 10 goals to this habit. / Vincule até 10 metas a este hábito.
habits.form.tagNameRequired Tag name is required Dê um nome à tag Give the tag a name / Dê um nome à tag
habits.form.tagNameTooLong Tag name must be 50 characters or fewer O nome da tag pode ter no máximo 50 caracteres Shorten the tag name to 50 characters or fewer. You entered {TotalLength}. / Encurte o nome da tag para até 50 caracteres. Você digitou {TotalLength}.
habits.form.tagColorInvalid Please choose a valid tag color Escolha uma cor de tag válida Choose a color from the swatches / Escolha uma cor da paleta
habits.form.checklistItemLimit You can add up to 50 checklist items Você pode adicionar até 50 itens no checklist Add up to 50 checklist items / Adicione até 50 itens ao checklist
habits.form.checklistItemTooLong Checklist items must be 500 characters or fewer Cada item do checklist pode ter no máximo 500 caracteres Shorten each checklist item to 500 characters or fewer / Encurte cada item do checklist para até 500 caracteres
goals.form.titleRequired Title is required Dê um título à meta Give the goal a title / Dê um título à meta
goals.form.titleTooLong Title must be 200 characters or fewer O título pode ter no máximo 200 caracteres Shorten the goal title to 200 characters or fewer. You entered {TotalLength}. / Encurte o título da meta para até 200 caracteres. Você digitou {TotalLength}.
goals.form.targetValueRequired Quantity must be greater than 0 A quantidade precisa ser maior que 0 Set the target to a value greater than 0 / Defina a quantidade da meta como um valor maior que 0
goals.form.unitRequired Unit is required Defina a unidade Set the unit / Defina a unidade
goals.form.unitTooLong Unit must be 50 characters or fewer A unidade pode ter no máximo 50 caracteres Shorten the goal unit to 50 characters or fewer. You entered {TotalLength}. / Encurte a unidade da meta para até 50 caracteres. Você digitou {TotalLength}.
goals.form.habitLimit You can link up to 20 habits Você pode vincular até 20 hábitos Link up to 20 habits to this goal. / Vincule até 20 hábitos a esta meta.
profile name tooLong Keep it under 50 characters. Use no máximo 50 caracteres. Use 50 characters or fewer. / Use no máximo 50 caracteres.

External interface evidence

The FluentValidation version installed here is 12.1.1. Its own lib/net8.0/FluentValidation.xml defines DefaultValidatorOptions.WithMessage, WithState, and ValidationFailure.CustomState / FormattedMessagePlaceholderValues. WithMessage applies only to the preceding rule; the field attachment deliberately does not call WithErrorCode.

An actual validator result printed by ValidationLocalizationTests during this round had the following complete shape, showing the FluentValidation fields consumed by localization. This is real producer output, not a hand-authored fixture:

[{"PropertyName":"Items[0]","ErrorMessage":"Write the checklist item","AttemptedValue":"","CustomState":{"Values":[],"CopyKey":null},"Severity":0,"ErrorCode":"VALIDATION_TEMPLATE_ITEM_REQUIRED","FormattedMessagePlaceholderValues":{"CollectionIndex":0,"PropertyName":"Items","PropertyValue":"","PropertyPath":"Items[0]"}}]

Re-derive validator results with env -u LANG dotnet test tests/Orbit.Infrastructure.Tests --filter FullyQualifiedName~EveryValidatorFamily_ReturnsPortugueseCopyAndAlignedCodes --logger "console;verbosity=detailed"; the existing test prints the real errors. The new field tests drive real validators through ValidationExceptionHandler and check the produced HTTP messages and codes in both request languages. Their inputs use the repository's own commands rather than an invented external response.

Test evidence

Before modifying tests or implementation:

  • env -u LANG dotnet test tests/Orbit.Application.Tests --filter 'FullyQualifiedName~ErrorCopyTests|FullyQualifiedName~ValidatorTests': exit 0, 1,725 passed with the defect present.
  • env -u LANG dotnet test tests/Orbit.Infrastructure.Tests --filter FullyQualifiedName~ValidationLocalizationTests: exit 0, 35 passed with the defect present.

Stronger regression before the implementation fix:

  • env -u LANG dotnet test tests/Orbit.Infrastructure.Tests --filter FullyQualifiedName~PersonFacingValidators_ReturnLocalizedInstructionsWithUnchangedCodes: exit 1, 164 failed and 14 passed out of 178. Failures showed the real old rule strings, including The length of 'Message' must be 5000 characters or fewer. You entered 5001 characters., instead of the expected localized field instructions.
  • The later inventory extension ran the same command before fixing tag suggestions, bulk tag names and counts: exit 1, 12 failed and 190 passed out of 202. The failing cases were both cultures of those six old-copy rules. The suggestion endpoint that already shared habit rules passed.

After the fix, the same regressions pass as part of the focused localization suite: exit 0, 245 passed. This includes 202 field cases, 8 checklist length cases and the existing 35 localization cases. The application catalog and validator suite passes: exit 0, 1,812 tests. Code-format assertions remain covered. Accepted values and exact allowed lengths are checked, not just rejection paths. The change was compiled and committed before broader verification, with hooks intact.

The table below is the run on commit 6eeaa91a.

Environment Build command Exit / errors Test command Exit / counts
LANG unset env -u LANG dotnet build Orbit.slnx 0 / 0 errors env -u LANG dotnet test 0 / 9,346 passed: 32 analyzers, 661 domain, 5,421 application, 3,232 infrastructure; 0 failed, 0 skipped
LC_ALL=en_US.UTF-8 env LC_ALL=en_US.UTF-8 dotnet build Orbit.slnx 0 / 0 errors env LC_ALL=en_US.UTF-8 dotnet test 0 / 9,346 passed: 32 analyzers, 661 domain, 5,421 application, 3,232 infrastructure; 0 failed, 0 skipped

Builds retain pre-existing package-version and obsolete-API warnings. Both commit-hook runs passed formatting, root allowlist, dash, timeless and suppression gates. The working tree is clean. No bare narration comments were found in the changed C# files. No generated architecture files were staged.

Review fix fd4dc203 (the English profile name limit, see Copy approval) was rerun by the orchestrator: env -u LANG dotnet build Orbit.slnx exit 0, env -u LANG LC_ALL=en_US.UTF-8 dotnet build Orbit.slnx exit 0, env -u LANG LC_ALL=en_US.UTF-8 dotnet test Orbit.slnx --no-build exit 0. The LANG unset full run failed one test outside this change, EventsControllerTests.Stream_SendsPublishedChangesOnce, on its 2 second hang guard while the machine ran another worker; env -u LANG dotnet test tests/Orbit.Infrastructure.Tests --no-build --filter FullyQualifiedName~EventsControllerTests then passed 12 of 12 in five consecutive runs. That flaky guard is filed for main separately.

Staging evidence

Both live five-digit requests returned HTTP 400 with the old messages. The branch has not been deployed, so these observations confirm the defect and response shape; they do not prove the new copy is live. Reproduce with the commands below, then compare the messages after deploying the reviewed commit.

curl --silent --show-error --request POST https://api-staging.useorbit.org/api/auth/verify-code --header 'Content-Type: application/json' --header 'Accept-Language: en' --data '{"email":"reader@example.com","code":"12345","language":"en"}' 
curl --silent --show-error --request POST https://api-staging.useorbit.org/api/auth/verify-code --header 'Content-Type: application/json' --header 'Accept-Language: pt-BR' --data '{"email":"reader@example.com","code":"12345","language":"pt-BR"}' 

Complete observed response shapes, with request ids redacted:

en:

{
  "type": "ValidationFailure",
  "status": 400,
  "requestId": "<redacted>",
  "errors": {
    "Code": [
      "'Code' must be 6 characters in length. You entered 5 characters.",
      "Code must be a 6-digit number"
    ]
  },
  "errorDetails": {
    "Code": [
      {
        "code": "ExactLengthValidator",
        "message": "'Code' must be 6 characters in length. You entered 5 characters."
      },
      {
        "code": "VALIDATION_VERIFICATION_CODE_FORMAT",
        "message": "Code must be a 6-digit number"
      }
    ]
  }
}

pt-BR:

{
  "type": "ValidationFailure",
  "status": 400,
  "requestId": "<redacted>",
  "errors": {
    "Code": [
      "'Code' deve ter exatamente 6 caracteres. Você digitou 5 caracteres.",
      "O código deve ter 6 dígitos"
    ]
  },
  "errorDetails": {
    "Code": [
      {
        "code": "ExactLengthValidator",
        "message": "'Code' deve ter exatamente 6 caracteres. Você digitou 5 caracteres."
      },
      {
        "code": "VALIDATION_VERIFICATION_CODE_FORMAT",
        "message": "O código deve ter 6 dígitos"
      }
    ]
  }
}

Copy approval

/second-opinion (Claude Opus 5.5) judged the claimed copy defect against every new en and pt-BR message in this change. The first pass on 6eeaa91a returned AGREE (medium): the English profile name message "Keep it under 50 characters." said below 50 while the rule and the pt-BR text allow 50. Review fix fd4dc203 changed it to "Use 50 characters or fewer.". The second pass on the complete fixed set returned DISAGREE (medium), so the copy is approved: each en and pt-BR pair matches in meaning, keeps the agreed terms (meta, tag, modelo de checklist, chave de API, código de indicação), uses correct placeholders and states each limit correctly. The verdict is also posted in this conversation.

Assumptions

  • Treat field copy keys as internal catalog selectors rather than new error codes. The rejected alternative would change errorDetails.code, which the ticket forbids.
  • Extend the existing invalid-text lookup on habit edits to descriptions and emoji rather than using the parent habit's wording for every sub-habit. Valid text keeps the existing fast path.
  • Update producer copy and read consumer sources for evidence rather than editing the consumer repository. The paired UI work owns its fixture refresh and client-key alignment.

Manual steps

  • Staging: after this merges to redesign/main, the orchestrator runs Release API with environment=staging and branch=redesign/main, then sends a five-digit code to POST /api/auth/verify-code in both languages and confirms HTTP 400 with "Enter all 6 digits" and "Digite os 6 dígitos" while the codes stay ExactLengthValidator and VALIDATION_VERIFICATION_CODE_FORMAT.
  • Production: this reaches production only with the redesign, through Release API with environment=production from main. No new configuration, secret, migration or backfill is required.
  • The UI follow-up thomasluizon/orbit-tickets#1205 aligns the client keys listed above, including the profile name tooLong key.

@thomasluizon

Copy link
Copy Markdown
Owner Author

Copy check, /second-opinion (Claude Opus 5.5), framed as a claimed copy defect against DESIGN.md ## Voice in orbit-ui-mobile:

Claim: the new shared templates are still defective person-facing copy, because pt-BR text carries the untranslated English property name (Preencha 'Subject'., Encurte 'Description' para até 10000 caracteres.), a quoted field name reads as machine text and is redundant beside the field it names (Enter 'Title'., Use a complete email for 'Email', like name@example.com.), and Please choose a valid tag color opens with filler and does not say what to choose.

Verdict: AGREE, high confidence. {PropertyName} is the unlocalized C# property name and the backend message overrides the client key beside the field, so pt-BR readers see mixed-language machine text.

So the copy is not approved yet. The specific pairs (Enter all 6 digits / Digite os 6 dígitos, the sub-habit limit, title and length pairs, the habit tag and goal limits, the goal habit limit) are not part of this objection. The fix goes to a review batch: field-specific localized messages on the person-facing validators, with the generic templates kept only for fields only a developer can trigger.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes: Reviewed the complete validation-copy rewrite and its regression tests, including the client error-handling paths in paired UI PR #1573.

  • Bilingual instructions: Replaces rule-style messages with actionable English and pt-BR instructions for verification codes, sub-habit fields, tag colors, linked-item limits, and shared FluentValidation templates.
  • Preserved contracts: Keeps validation rules, limits, error codes, formatting arguments, named placeholders, and response fields unchanged, including the two counted goal/tag errors.
  • Regression coverage: Adds exact verification-code copy assertions and exercises real login, API key confirmation, and account deletion validators through the HTTP exception handler in both locales.

Verification: 1,166 focused application tests and 38 middleware tests passed. The full solution build and full test suite were not independently rerun; staging verification and the separate copy-approval step described in the PR remain outside this review.

Pullfrog  | View workflow run | Using gpt-6.1-sol | 𝕏

@thomasluizon

Copy link
Copy Markdown
Owner Author

Copy approval for review batch 1 (6eeaa91a) and review fix fd4dc203, through /second-opinion (Claude Opus 5.5), framed as a claimed copy defect so that DISAGREE means approved.

  1. First pass on 6eeaa91a: AGREE, medium. "The ProfileNameLength en string 'Keep it under 50 characters.' tells the person to stay below 50, but the rule allows exactly 50. The pt-BR 'Use no máximo 50 caracteres.' states the rule correctly, so the two languages disagree."
  2. Fix fd4dc203: the English message is now "Use 50 characters or fewer." and its test row matches.
  3. Second pass on the complete fixed set: DISAGREE, medium. "Each person-facing en/pt-BR pair matches in meaning, keeps the stated terminology (meta, tag, modelo de checklist, chave de API, código de indicação), uses correct placeholders, and states the rules correctly."

Verdict: the copy is approved. The client key tooLong that still reads "Keep it under 50 characters." is listed for the UI follow-up thomasluizon/orbit-tickets#1205.

@sonarqubecloud

sonarqubecloud Bot commented Oct 4, 2026

Copy link
Copy Markdown

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes: Reviewed the changes since 382820d1, including field-specific localization, expanded sub-habit validation, and the inclusive profile-name limit.

  • Added field-specific instructions: Attached bilingual catalog selectors through internal validation state, preserving original error codes, named placeholders, limits, and HTTP response fields across the inventoried validator flows.
  • Expanded sub-habit copy selection: Included invalid descriptions and emoji in the existing ownership-scoped habit lookup while retaining the fast path for valid text.
  • Strengthened regression coverage: Added exact bilingual validator-to-HTTP assertions and accepted-boundary checks, including all three verification-code rules, suggestion and bulk flows, and checklist limits.
  • Corrected the profile limit: Made the English instruction explicitly allow 50 characters, matching the validator and Portuguese copy.

Verification: the complete solution build passed with zero errors, and all 9,346 tests passed with LANG unset. Also checked the shared validation schemas and backend-message mapping in paired UI PR #1573; the internal copy selectors do not change the consumer contract. Post-deployment staging verification remains outside this review.

Pullfrog  | View workflow run | Using gpt-6.1-sol | 𝕏

@thomasluizon
thomasluizon merged commit 5ca8bef into redesign/main Oct 4, 2026
26 checks passed
@thomasluizon
thomasluizon deleted the fix/ticket-1204-validation-fix-copy branch October 4, 2026 20:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant