The contacts manager in Settings was stuck at name+email inline only —
no address field, no phone input on add, no search to find anything in
a list of 100+ contacts.
UI:
- Add form gets phone and address inputs alongside name/email. The
email-required gate becomes name-OR-email so address/phone-only
entries are creatable.
- Edit form gets an address input, threaded into the PUT body.
- Search input above the list filters client-side by name / emails /
phones / address (debounced 80ms). Count badge shows N/M when a
filter is active.
Backend:
- /api/contacts/{uid} PUT now accepts address and routes it through
_update_contact (which already supports it after the previous
commit). Validation loosened: name OR email OR address.
- /api/contacts/add POST now accepts phone + address. Phone goes
through an immediate _update_contact since _create_contact's
signature only takes name+email+address.
Closes the gap that pushed the agent into manage_memory when the user
pasted an address and said 'save this for X'. manage_contact now
accepts an optional address arg end-to-end:
- routes/contacts_routes.py:
- _normalize_contact carries an 'address' field
- _build_vcard emits ADR:;;<address>;;;; (street component of the
RFC-6350 7-part ADR), only when address is non-empty
- _parse_vcards reads ADR, joins non-empty components with ', '
- _create_contact and _update_contact thread address through;
update preserves existing address when caller passes empty
- src/tool_implementations.py do_manage_contact:
- add accepts address; require at least name+address or email
(was: email required) so address-only contacts are addable
- update accepts address; require name OR emails OR address
- src/tool_schemas.py: schema gets a single 'address' string field
- src/tool_index.py + src/agent_loop.py: descriptions get one
'address' arg mention and a 'use this for save-X-for-person /
address pastes / phone-with-name' steering line. Net: a few
bytes added, not a paragraph.
Also: removed a stray name from the schema's manage_contact example
strings ('save Jonathan's email…') — no real names in the codebase.
* refactor(constants): single source of truth for data dir + merge core/src constants
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(contributing): use named src.constants for data paths, drop core/constants references
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_parse_vcards matched property names with a bare line.startswith("EMAIL") /
"TEL" / "FN:" / "UID:". RFC 6350 property groups — emitted by default by Apple
Contacts / iCloud and many CardDAV servers — prefix the name with a group token,
e.g. item1.EMAIL;type=pref:jane@example.com. Those lines never matched, so emails
and phone numbers from any Apple-synced or Apple-exported address book were
silently dropped (breaking contact search by email, composer autocomplete, and
vCard/CSV export round-trips).
Strip an optional leading group token before matching and value extraction;
no-op for non-grouped lines.
Adds tests/test_contacts_vcard_parse.py (grouped + plain) — the grouped case
fails before this change and passes after.
Co-authored-by: NubsCarson <nubs@nubs.site>