Repository navigation
fix(platform): render any stored contact address in the details dialog - #4224
Conversation
yannickmonney
left a comment
There was a problem hiding this comment.
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.
updateContactecho 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).
7b9ee78 to
e23bee5
Compare
c845d50 to
10485d4
Compare
10485d4 to
d339eff
Compare
What changed
A contact's
addressis a bounded free-form object on both doors (boundedJsonObjectinbackend/domains/contacts/input-schema.ts;freeFormObjectin the OpenAPI spec), so{"street": {"line1": "One Test Street"}, "country": "Switzerland"}is a valid stored address.ContactViewDialoghanded 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 ornullreads as nothing. The walk keeps its own stack, as the door's bound check does, so no stored nesting can exhaust the call stack.ContactViewDialogrenders 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.app/lib/backend/contract/contacts.ts): the read shape now typesaddressas the door stores it,Record<string, unknown> | null, so the compiler refuses a field rendered unread. TheupdateContactecho now reusesContactRecordinstead of a second copy of the narrow type. The write args are unchanged; no app form writesaddress.tests/manual/reference/automation.mdnow 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.ErrorBoundaryBase. The flat-string control and the existing tests passed.contact-view-dialog.test.tsx+contact-data.test.tspass 32/32, with no React-child error in the log. The regression case feeds the API's readback row through the realcontacts/queries:listContactsPaginatedadapter (stubbedfetch) into the dialog. It asserts the linesOne 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.app/features/contactspluscontact-recipient-pickerandcompose-email-panepass 14 files / 124 tests.app/lib/backend/engagement.test.ts(server project) passes 14/14.tscprogram over the changed files, every file touching the contacts contract or helpers, and the platform.d.tsfiles reported 0 diagnostics. A probe confirmed the type change has teeth: the base dialog, checked against the new contract, failsTS2322at its seven address renders (lines 97–111).oxlint --type-awareandoxfmt --checkon the changed files are clean, as arebun run knip:check,bun run lint:manualandcheck-guide.tson the knowledge suite.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
c845d50f55292cf42ae9628ff79531af70bb8471onto maind1373d84cd56972501403f62145ec52e6f65d44a, 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.