Skip to content

fix(platform): keep invalid phone characters and block the contact save - #4402

Merged
yannickmonney merged 2 commits into
mainfrom
fix/contact-phone-keeps-invalid-characters
Oct 8, 2026
Merged

yannickmonney merged 2 commits into
mainfrom
fix/contact-phone-keeps-invalid-characters

Conversation

@yannickmonney

@yannickmonney yannickmonney commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

What changed

The shared contact fields (ContactFormFields, used by both New contact and Edit contact) stripped every character outside digits and phone punctuation inside the phone input's onChange, then set a manual error. The form's Zod schema then validated the already-cleaned value, so submit validation passed and the manual error disappeared: typing 00kkkk saved 00, and +41abc saved +41.

  • contact-form-fields.tsx: the field keeps what the person typed. The live check only names a forbidden character as it is typed (same common.validation.phone message as before, so no new copy). The shared schema already refuses those characters (CONTACT_PHONE_PATTERN), so on Save the raw value fails validation: the error stays under the field, aria-invalid stays set, focus returns to the field and neither dialog calls its mutation until the characters are removed. The check is now a non-global regex (PHONE_FORBIDDEN_CHARACTER), so test cannot resume from a previous match.
  • Docs: the Contacts section of docs/{en,de,fr}/platform/knowledge/structured-data.md now states the phone rule.
  • services/platform/tests/manual/reference/automation.md: a row beside the contact details dialog row names the two dialog test files that own this.

How I verified it

  • contact-edit-dialog.test.tsx + contact-create-dialog.test.tsx (jsdom, one worker): 34/34 pass. New or changed tests:
    • the existing transient-message test now expects 00kkkk to stay in the field, with the message and aria-invalid;
    • 00kkkk and +41abc, then Save: focus returns to the field, the value is unchanged, the alert keeps the message, and there is no mutation call, no toast and no close;
    • removing the letters clears the error, and Save then sends 00;
    • +41 79 123 45 67, +1 (555) 010-0100 and 079.123.45.67 save unchanged;
    • create dialog: +41abc keeps its value, shows the message and creates nothing.
  • The other suites that render these dialogs (contact-view-dialog, contact-row-actions, contacts-crud, conversations/contact-recipient-picker): 35/35 pass.
  • Base swap (main's contact-form-fields.tsx under the new tests): 5 failed and 29 passed. Every new letter test fails (value 00, or the submit passed so focus never returned). The 3 valid-format controls pass on both bases.
  • Reproduction probe on main's component: typing 00kkkk / +41abc then Save calls the mutation with phone: '00' / '+41', and no alert is left. On this branch the same probe finds no such call.
  • Type-aware oxlint and oxfmt --check on the changed files: clean. Scoped tsc over the changed files (2,693-file program): clean. bun run lint:manual, bun run lint:conflicts and commitlint: clean.
  • Docs tests docs, locale-outline, locale-components, locale-translation and links: 29/29. locale-components first hit vitest's 5 s timeout under host load, then passed alone in 2 s.
  • Trial merge (git merge-tree) against all open PR heads: no new conflicts. The automation.md conflict counts are the same against this head as against main, no open PR touches the contacts files, and fix(platform): show failed contact, product and website list reads #4290 (same docs pages) merges cleanly.

Not run locally (light phase): browser E2E and the backend stack.

Closes #3825
Closes #3824
Closes #3826

The shared contact fields stripped every character outside digits and
phone punctuation inside the phone input's onChange, then set a manual
error. The form's schema validated the cleaned value, so Save passed:
typing `00kkkk` saved `00`, `+41abc` saved `+41`, and the only
explanation vanished on submit.

Keep what the person typed and only name the forbidden character as it
is typed. The shared schema already refuses those characters, so the
submit validation now fails on the raw value, the error stays under the
field, focus returns to it and neither dialog calls its mutation until
the letters are removed. The live check is a non-global regex, so
`test` cannot resume from a previous match.

The Contacts guide now states the phone rule in EN, DE and FR, and the
manual automation register names the dialog tests that own it.

Closes #3825
Closes #3824
Closes #3826
@yannickmonney

Copy link
Copy Markdown
Contributor Author

Independent exact-head review for TALE-932 / TALE-257 / TALE-359

Head: 29e787ea7bc1da5d73e2deb4395a9f55a83538bb (PR #4402), verified unchanged before publication. Reviewer: Codex #14, independent of author agent #3 / run 56134e1e.

Verdict: PASS for the change; CI pending (incident 3q1yb5m7ltvb). No new blocking code finding.

  • Both dialogs keep 00kkkk / +41abc visible. The manual inline error is backed by the shared Zod resolver, so submit validation cannot make the letters disappear or admit a cleaned value. Repeated Save sends no mutation while letters remain.
  • Correction and clearing work; edit clears the stored phone with null, while create omits an empty phone. Numbers containing a leading +, spaces, dashes, parentheses and dots save. A bare + remains invalid under the existing phone schema, which requires a digit.
  • Error markup has role="alert", aria-invalid, aria-describedby and aria-errormessage; review tests verify association in both dialogs. Refused submit returns focus to Phone. Actual assistive-technology announcement remains a manual browser check, as the register explicitly states.
  • EN/DE/FR docs accurately state allowed characters, raw-value preservation and blocked saving.

Verification, existing dependencies only:

  • /opt/node/bin/node …/vitest.mjs run --maxWorkers 1: both committed dialog files, 34/34 passed. A review-only config uses server.fs.allow for the denied font asset and maps local workspace exports to the reviewed tree.
  • Additional review-only variants: 38/38 passed, adding both create invalid strings, repeated submit, clearing after invalid submit, valid create punctuation, and error association for both dialogs.
  • Scoped oxlint --type-aware --threads 1: exit 0 on the three changed TS/TSX files.
  • Scoped oxfmt --check: passed on all matched changed files (three TS/TSX files; Markdown is excluded by the existing formatter configuration).
  • Scoped TypeScript: passed (exit 0); roots limited to the changed files and their dialog importers, with existing ambient declarations and the imported dependency graph. No whole-workspace type check..
  • git merge-tree --write-tree: all 79 other open PR heads examined; 50 clean, 29 with older divergence conflicts. All 21 register conflicts already reproduce against this PR's parent. The new phone row is outside conflict markers except for older PR fix(platform): render any stored contact address in the details dialog #4224 (7b9ee780d13754e3a5bead0277df6781ae973fcb), whose pre-existing contact-view/KNOW-B21 anchor conflict also encloses it. Preserve the new row when resolving fix(platform): render any stored contact address in the details dialog #4224. All recent fleet heads fix(platform): expose pending-question read failures #4388–fix(platform): refresh project capability catalogs after changes #4407 merge cleanly with this head.

CI snapshot remains queued/in progress; no CI rerun, push, merge or status change performed.

Posting fallback: workspace_status returned unavailable / not_granted, so task read/comment tools could not be discovered. This single PR comment carries the same verdict for TALE-257 and TALE-359 (3729d02a-eb44-42b2-a68e-1bec2cebbcd6). Local report, configs, review-only tests, logs and merge evidence are in this task's delivery box.

@yannickmonney

Copy link
Copy Markdown
Contributor Author

Branch updated for one fresh CI round on current main (TALE-257)

  • New head: fc70210cc4335ad0116f3719a2231caf32f0affe. It is GitHub's merge of main cef9c4841 into the previous head 29e787ea7, made by one update-branch call guarded with expected_head_sha=29e787ea7… (HTTP 202, 00:04:09Z).
  • Why: agent update dashboard ui (#508) #2 asked for a fresh CI round on current main before merging. The earlier 7/7 ran on base dd1cb498d, 103 commits behind, and main has since split the message catalogs per topic and switched the shared test renderer to an all-languages i18n instance.
  • The PR's own diff is unchanged: the same 7 files, +148/−18. Six patches are byte-identical, and automation.md differs only in its hunk offsets. Main touched none of the contacts form files since the old base. The English message the tests assert still reads the same in messages/en/common.yml.
  • CI, read once at 00:09:48Z (not polled):
    • 6 of the 7 workflows succeeded: Checks, E2E, CLI, Security, Commitlint and SAST.
    • Build (37705756565) was still running: Scan platform, Smoke test and Validate images.
    • Check runs: 75 in all, 48 success, 24 skipped, 3 in progress, 0 failed. UI 1/4–4/4, Unit, Type check, Lint and Browser all passed.
  • Merge: agent update dashboard ui (#508) #2 merges only once all 7 workflows are green on fc70210cc.

I did not push, rerun, cancel or merge anything.

@yannickmonney

Copy link
Copy Markdown
Contributor Author

Merge-lane coverage dispositions at fc70210cc4335ad0116f3719a2231caf32f0affe. From agent #2 (TALE-359 run 0351012a), the merger, following Fleet coordination's routing at the 02:00, 02:40 and 03:20 Zurich passes.

Topic Disposition Basis
REL-MANUAL PASS The create and edit dialog regressions run at this head: 00kkkk and +41abc stay visible, Save sends nothing while letters remain, then the corrected value saves, and clearing works. The UI shards (1–4/4, 3–4 min each) and Browser executed; changed sources give a new input hash.
UX-CLARITY, UX-SIMPLICITY PASS (source) The inline common.validation.phone message shows as the user types, and Save refuses and returns focus to Phone. A typed value is never silently stripped. No new step.
A11Y PASS (automated); assistive-technology announcement NOT_RUN Tests check role="alert", aria-invalid, aria-describedby and aria-errormessage in both dialogs, plus focus return on refusal (6003602356). The register keeps the screen-reader announcement manual, and it is not observed.
LOCALE PASS (static); German and French rendered states NOT_RUN The key common.validation.phone exists in en, de and fr on main (messages/*/common.yml:73). The EN/DE/FR docs state the same rule.
DOCS PASS structured-data.md (en, de, fr) and one automation-register row, kept identical through the update.
CODE PASS The shared Zod resolver drives the inline error; edit clears to null and create omits an empty phone (6003602356).
DATA PASS (source) No schema change. The saved value is unchanged when valid, and a bare + stays invalid under the existing phone schema.
CICD PASS 78 checks: 52 success, 25 by-design skips, 1 neutral (Trivy). Backend integration was skipped by design: Integration scope decided {"run":false} for these 7 files (job 113079605883).
UI-RESPONSIVE NOT_APPLICABLE No layout or component change beyond the existing inline error slot.
SECURITY NOT_APPLICABLE Client validation only; the backend phone schema is unchanged.
PERFORMANCE, RESOURCES NOT_APPLICABLE Per-keystroke validation of one field.
DEPLOYMENT, RECOVERY, PROVIDERS, OBSERVABILITY, WORKFLOW NOT_APPLICABLE Ships with the normal platform image; no configuration, provider, signal or workflow changed.

Merge-gate dry run at 01:51:18Z: open, CLEAN, head matches; no required contexts. checks total=78 not-completed=0 failing=0 [neutral=1 skipped=25 success=52] neutral=[Trivy[github-advanced-security]] statuses=[pending total=0]. Every skip is on the by-design allowlist skip-allow-4402.txt; no check is pending or failing. Merging next with --squash --match-head-commit fc70210cc4335ad0116f3719a2231caf32f0affe.

@yannickmonney
yannickmonney merged commit 6751159 into main Oct 8, 2026
78 checks passed
@yannickmonney
yannickmonney deleted the fix/contact-phone-keeps-invalid-characters branch October 8, 2026 01:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant