Skip to content

fix(platform): render any stored contact address in the details dialog - #4224

Merged
yannickmonney merged 1 commit into
mainfrom
fix/contact-address-nested-render
Oct 9, 2026
Merged

yannickmonney merged 1 commit into
mainfrom
fix/contact-address-nested-render

Conversation

@yannickmonney

@yannickmonney yannickmonney commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

What changed

A contact's address is a bounded free-form object on both doors (boundedJsonObject in backend/domains/contacts/input-schema.ts; freeFormObject in the OpenAPI spec), so {"street": {"line1": "One Test Street"}, "country": "Switzerland"} is a valid stored address. ContactViewDialog handed every address field to React as a child, trusting a client type that narrowed each field to a string. A nested object threw "Objects are not valid as a React child (found: object with keys {line1})", and the dialog's error display replaced every detail of the contact (#3625).

  • getContactAddressLines (app/features/contacts/lib/contact-data.ts) reads an address at run time. It returns the street, then city and state together, then the postal code and the country, each as its words. A string or number reads as written. A nested object or list reads as the words of its values, in order and comma-joined. A blank, a flag or null reads as nothing. The walk keeps its own stack, as the door's bound check does, so no stored nesting can exhaust the call stack.
  • ContactViewDialog renders those lines. A field with no words is left out, and the Address fact is omitted when no line is left (it used to render an empty block for {}). Keys outside the five are still stored but not shown, as before.
  • Contract (app/lib/backend/contract/contacts.ts): the read shape now types address as the door stores it, Record<string, unknown> | null, so the compiler refuses a field rendered unread. The updateContact echo now reuses ContactRecord instead of a second copy of the narrow type. The write args are unchanged; no app form writes address.
  • Manual register: the contact details row in tests/manual/reference/automation.md now names the address behaviour and both owning specs.

No user-visible string was added or changed, so no locale is touched. No docs page describes how the dialog shows an address. The API reference's free-form contract stands unchanged, and the app now honours it.

How it was verified

Every check below ran locally on the tree committed as 7b9ee780d13754e3a5bead0277df6781ae973fcb.

  • Red first: the new tests on the unfixed dialog failed 4 of 19 with the issue's exact error, caught by ErrorBoundaryBase. The flat-string control and the existing tests passed.
  • Green: contact-view-dialog.test.tsx + contact-data.test.ts pass 32/32, with no React-child error in the log. The regression case feeds the API's readback row through the real contacts/queries:listContactsPaginated adapter (stubbed fetch) into the dialog. It asserts the lines One Test Street / Switzerland, the heading, the email and the ID, and no Try again. The flat address stays the control (One Test Street / Zurich, ZH / 8001 / Switzerland). There are also cases for lists, numbers, deeper nesting, wordless fields, an address with nothing to show, and an axe audit over a nested address.
  • Importers: all of app/features/contacts plus contact-recipient-picker and compose-email-pane pass 14 files / 124 tests. app/lib/backend/engagement.test.ts (server project) passes 14/14.
  • Types: an isolated tsc program over the changed files, every file touching the contacts contract or helpers, and the platform .d.ts files reported 0 diagnostics. A probe confirmed the type change has teeth: the base dialog, checked against the new contract, fails TS2322 at its seven address renders (lines 97–111).
  • Lint and format: oxlint --type-aware and oxfmt --check on the changed files are clean, as are bun run knip:check, bun run lint:manual and check-guide.ts on the knowledge suite.
  • The act(...) warnings in the test output are pre-existing: 16 for the dialog and 58 in total, before and after.

Not run here: the full platform suite, a whole-workspace tsc, a browser, E2E or a stack. CI owns those.

Closes #3625

Current-main rebase

Replayed the previously accepted source c845d50f55292cf42ae9628ff79531af70bb8471 onto main d1373d84cd56972501403f62145ec52e6f65d44a, including the merged shared CI repair in #4625. The accepted feature payload and all current-main changes are preserved in one atomic commit. Configured commit and conflict checks pass; earlier behavioral proof remains recorded above. All seven native required checks and full merge-group validation remain required for this new source.

Maintenance replay: preserves the accepted feature payload on current main 7d178ca. Includes the merged #4649 Knip cleanup and the exact independently accepted one-line shared CLI inventory repair from #4654 (252f0df), which is still pending native merge on main. The fixed suite inventory keeps its discovery and source/compiled phase guards. Existing feature proof is retained; no fresh full-feature/full-workspace test or hosted-green claim. Native required checks remain mandatory.

@yannickmonney yannickmonney left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Independent review — ACCEPT (source and targeted local proof)

PR #4224 / TALE-170 / #3625, exact head 7b9ee780d13754e3a5bead0277df6781ae973fcb, base 9b7463b707c5e44349f0a8c51e764a39e3e5c4fb.
Reviewer: agent 7b86c233-981a-48e1-875f-7973b22a525e, run 17f16e23-a299-489c-8597-81f9f8a436e3 (TALE-547), distinct from implementation agent 5f5307c9-4cbd-416a-a456-8bb667fc584d, run 0db6326f-878a-4a9d-bd10-7f93a142c8fe.
Evidence consumed: TALE-359 comment cd314f5a-468e-4866-8fce-da7d4596a4de, TALE-170 acceptance description, issue #3625 and the six-file exact-head diff. Head confirmed at start and again immediately before this record.

Findings and acceptance

No blocking finding in this scope.

  • Nested objects/lists are converted to strings before React receives them. Nulls, flags, blanks and empty containers cannot become invalid React children; unrelated contact details remain visible. Unknown top-level keys remain undisplayed. Markup-looking values remain escaped text.
  • For accepted stored JSON, the walk terminates with O(N) traversal and memory, not recursive call-stack usage or exponential expansion. Both write doors use the same boundedJsonObject: 65,536 UTF-8 bytes, depth 8, 500 object keys. Arrays are not key-count-bounded but are byte-bounded. Each node of a serialized JSON tree is pushed/popped once; the five displayed fields are separate subtrees. This argument does not claim protection against arbitrary cyclic JavaScript objects/getters, which cannot be stored/read back as JSON.
  • The flat-string control retains street / city-and-state / postal-code / country ordering. Empty Address facts are intentionally omitted.
  • The widened read type is consistent with stored free-form values. App-wide property/bracket searches find no remaining raw contact-address renderer; edit forms do not read or rewrite this object. updateContact echo reuses the same read shape; write arguments are unchanged. Scoped type checking includes contact consumers, helper consumers, adapters, the route and required global declarations.

Independently observed proof

All testing is local, synthetic, one-worker and uses retained dependencies (no installation):

  • Existing dialog/helper specs: 32/32 passed.
  • Independent jsdom matrix: 76/76 passed — all five displayed keys crossed with null/booleans/numbers/strings/empty/nested objects and arrays/markup-like strings, plus null address, 16,000 scalar array entries, 21,000 empty containers, 500 total object keys, maximum accepted nesting and hostile-looking JSON keys. Every matrix address passes the real contact input schema before reaching the actual dialog; checks retain the heading/email, exact address lines and no retry/error display or injected image.
  • Contacts plus recipient-picker/compose-email importers: 15 files / 200 passed, including the 76 independent cases (124 existing tests).
  • Pagination/adapters: 14/14 passed.
  • Baseline dialog substituted only in an owned disposable snapshot: 4 failed / 15 passed, including the exact invalid React-child error; flat-string control stays green. Restored head dialog: 19/19 passed. Hash comparison verifies all six changed files match the exact reviewed commit.
  • Scoped TypeScript: 34 root files, 0 diagnostics. No whole-workspace type check. Initial harness omissions of router/global environment registration and a wrong CWD were corrected and retained in the logs; they are not PR findings.
  • Targeted type-aware oxlint exits 0, no diagnostics emitted. Targeted oxfmt check passes on the five TypeScript files; diff whitespace check is clean. UI dependency source is byte-identical to the snapshot despite using retained workspace dependency links.
  • Read-only merge-tree succeeds against fetched main 1a2a34211dd878871770cb9b527a075c7f02f45f.

Limits and handoff

Passive CI snapshot at 03:43Z: five checks pending, not terminal CI acceptance. No CI rerun/cancellation/watch was initiated by this reviewer. This acceptance is not merge clearance or a native task approval; TALE-359 retains terminal required-CI/head/landing reconciliation.

Not run: real browser/visual gate, keyboard or screen-reader manual round, E2E, live API/PostgreSQL readback, full stack/containers, full platform suite, whole-workspace tsc, local SAST or a rerun of the author's knip/manual-guide gates. Jsdom schema/adapter proofs do not claim these.

Disk stayed above the 20 GiB floor (223 GiB initially; 220 GiB latest). No repository source edits, push, merge, card moves, native approval bypass or author-run interference. Deliverable directory: /agent/output/acb9e7e2-0e32-4acc-8435-2b541e62c3d2/ (verdict, probes/harness, raw red/green logs, type/gate logs, source hashes and CI/merge snapshots).

@yannickmonney
yannickmonney force-pushed the fix/contact-address-nested-render branch from 7b9ee78 to e23bee5 Compare October 9, 2026 02:57
@yannickmonney
yannickmonney force-pushed the fix/contact-address-nested-render branch 3 times, most recently from c845d50 to 10485d4 Compare October 9, 2026 14:04
@yannickmonney
yannickmonney force-pushed the fix/contact-address-nested-render branch from 10485d4 to d339eff Compare October 9, 2026 14:59
@yannickmonney
yannickmonney added this pull request to the merge queue Oct 9, 2026
@yannickmonney
yannickmonney merged commit 10c6e0e into main Oct 9, 2026
64 checks passed
@yannickmonney
yannickmonney deleted the fix/contact-address-nested-render branch October 9, 2026 22:20
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.

Bug: A valid free-form contact address can crash the contact details dialog

1 participant