Chat on repository - #777
Merged
Merged
Conversation
…ntry projection Additive foundation for migrating the contact book onto ContactRepository: - ContactName.resolveDisplayName/initials (homebase-api), shared by all consumers. - Contact.toContactBookEntry() projection in core (server-shaped Contact -> flat UI model), display name via the shared resolver, image fields from ContactImageRef. No consumers rewired yet; nothing removed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewire the contact book onto the single source of truth and delete the core-side read/write wrappers: - ContactBookViewModel + ContactDetailViewModel inject ContactRepository instead of ContactBookStream + ContactBookService. They project repo.contacts (List<Contact>) -> ContactBookEntry via the new projection; reads use repo.isLoaded/ensureLoaded; writes use repo.save/delete/sync/setImage with the optimistic update owned by the repo (no more stream insert/remove dance). - ContactSaveHelper saves through ContactRepository; ContactSaveResult.Success no longer carries an optimistic entry. - Delete ContactBookStream + ContactBookService; AppModule registers neither and the post-auth bootstrap starts ContactRepository. - ContactDetailViewModel now ensureLoaded()s on deep-link. Main-parity (the §7/#6 fixes and shared helpers are re-applied next). Core + Konsist + api jvmTest suites pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rser - §7: contact detail "Contact details" section lists the Homebase ID, so an identity-only contact no longer reads "None". - #6: no-clear merge mitigation on the repository path — saveContactDraft now coalesces the saved content (keeps the old value for a blanked field, matching the server's no-clear merge, so the optimistic entry doesn't flash empty then reappear) and reports clearedFieldsIgnored; both the list and detail screens surface a "Clearing a contact field isn't supported yet…" snackbar. - Remove the now-dead HomebaseFile.toContactBookEntry (the repo path parses to Contact via toContact(); the UI projects Contact.toContactBookEntry) and its now-unused imports; refresh the ContactBookEntry KDoc. Core + Konsist + api jvmTest suites pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ry; delete DriveContactService Migrates the chat contact read+write path onto the single ContactRepository source of truth and removes the duplicate chat contact stack: - ContactUiModel gains a Contact.toContactUiModel() projection (server-shaped Contact -> connection-oriented chat model), via the shared resolveDisplayName/ initials helpers. - ContactService now sources raw contacts from ContactRepository.contacts and layers connection state on top (unchanged enrichment); it no longer starts a drive reader (the repo is started by the post-auth bootstrap). - ConnectionRequestService writes contacts on connection accept/finalize via contactRepository.sync(odinId) instead of DriveContactService.saveContactForOdinId. - Delete DriveContactService and the entire chat duplicate model family (ContactServerFile, ContactName/Phone/Email/Location/Birthday, ContactImage, ContactProtocol, ContactSizer); drop its DI registration. Now both chat and the contact book read/write through one ContactRepository: one drive query, one event observer, one writer. Chat + core + Konsist + api jvmTest suites pass. NOT yet device-validated — the chat people-pickers (new conversation, select/add members, conversation list) read through ContactService and need a smoke test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t/last name - Danger zone is now set off by a HorizontalDivider (clearer separation). - "Contact details" lists labeled rows (overline label + value): First name, Last name, Homebase ID, Phone, Email, Location, Birthday — name parts first so an identity contact shows real details, not just the Homebase ID; the rest tuck behind the existing "More" toggle. Reuses the edit-form label strings + a new contactbook_detail_location. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ew screen The minimal ContactInfoScreen (avatar + name + odinId) shown for a 1:1 conversation / group-member tap is a strict subset of the contact-detail screen, which already loads the 1:1 conversation overview (recent media, groups-in-common, circles) plus the full contact fields, message/connect, and danger zone. Converge on one screen: - Both nav sites (1:1 conversation info, group-member tap) now navigate to Route.ContactBookDetail(uniqueId = md5(odinId), odinId) instead of Route.ContactInfo. - Delete ContactInfoScreen/ViewModel/UiState/UiAction, Route.ContactInfo, and the DI registration. The chat-side ShowContactInfo actions/events are unchanged — they just deliver an odinId to the screen callback, which now opens the contact detail. Core + chat + Konsist jvmTest suites pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The bug: opening a 1:1 chat and tapping the header (or the overflow "Conversation info", or long-pressing it in the list) fired ShowConversationSettings, which opened the conversation-overview/settings screen — not the contact detail. The earlier ContactInfo rewire only covered reaction taps / group members, missing this main path. handleShowConversationSettings now routes a 1:1 (non-group, non-self) to NavigateToContactInfo(peerOdinId) -> the full contact-detail screen. Groups still open group settings; note-to-self keeps the conversation-settings screen (no contact). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… settings) RecentMediaSection now shows "See all" whenever the 1:1 has ANY shared content (media, files, audio, dice rolls, or locations) — not just media — so non-media items are reachable even with no media to strip; the empty state shows only when there's truly nothing. "See all" already routes to the same ConversationMedia screen the conversation-settings overview used, so the contact detail now fully subsumes that overview with nothing lost. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire clearKeyCache through reset(), evict the stale per-uniqueId AES key on delete, make the deletedIds resurrection guard race-safe, and lift delete suppression before sync re-creates a contact. Adds ContactRepositoryTest covering the load/delete/sync paths. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend ContactContent with the remaining header fields (shortBio, nickname, status, link, social, isEmergencyContact). social and isEmergencyContact are nullable so they omit-on-null and don't clobber stored values through the server's field-level UPDATE merge. Add the ext_data payload read path: ContactExtData (raw JsonElement map, forward-compatible) with lazily-decoded Experience and Bio types, disambiguating the short_bio string-vs-richtext collision by attribute type id. Contact now carries fileId/keyHeader/hasExtData so the bios are fetched on demand without re-reading the file header; the fetch goes through a narrow ContactPayloadReader seam (mirrors ContactHeaderReader) backed by DriveFileProvider.getPayloadBytesDecrypted. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add ContactRepository.emergencyContacts, a StateFlow subset of contacts flagged as emergency contacts, derived from the live list so it tracks the same optimistic writes and sync reconciliation. Make isEmergencyContact a plain non-null Boolean (you either are one or you're not) with @EncodeDefault(NEVER) so the false default is omitted — keeping the field-level UPDATE merge non-destructive — while true is written. Clearing the flag is not expressible through the merge and needs a dedicated write. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contact detail UI: - Show the free-text status under the contact's odinId in the header. - New "Bio" (shortBio) and "Social" sections under contact details. - ContactAttributes.kt: well-known attribute-type GUIDs + ContactSocialNetwork enum; socialHandles() resolves ContactContent.social, normalizing the dashless 32-hex stored key form against the hyphenated constants. ContactSocialTest pins it. Per-app contact data: - setAppData/deleteAppData (inline tier) and setAppExtData/deleteAppExtData + loadAppExtData (bulk ext_data tier) on ContactRepository, over the new ContactsProvider app-data/app-ext-data endpoints (SetContactAppDataRequest). - ContactContent.appData inline map; Contact.payloadKeys replaces hasExtData so a reader can skip a guaranteed-404 fetch for any absent payload. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cover the per-app contact app-data client landed in ed2015b: the four ContactsProvider writes (shared body with no appId, inline vs bulk tier paths, version-gated retry, size-cap 400 -> MaxContentLengthExceeded), the appId normalization + inline/bulk read helpers, and ContactRepository bulk read + the too-large -> ContactAppDataTooLargeException translation. Adds a ProblemDetails fixture for the size-cap case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Receive-side of the emergency-contact handshake:
- New StatusMessage.EmergencyContactDesignated discriminator.
- ConversationStream gains onEmergencyContactDesignated + a dispatchEmergencyDesignations
pre-pass (live BatchReceived only, receiver-side filtered) mirroring the GroupHealRequested
hook.
- AppModule wires it to ContactRepository.setEmergencyContact on the sender's contact
(md5(odinId)) on our own drive: ensureLoaded, idempotent, sync-if-absent, Forbidden-safe.
- MessageMapper renders a system line "X added you as an emergency contact" (self/subject
variants); 3 new strings.
ContactRepository.setEmergencyContact(uniqueId, versionTag): minimal-delta write that sends
only {"isEmergencyContact": true} and optimistically copies the flag onto the live contact's
existing content (set-only; the merge can't clear it). Covered by a new ContactRepositoryTest.
Also: pre-normalize ContactSocialNetwork attribute ids via normalizeId at construction.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ConversationService.sendEmergencyContactDesignation(recipient): gets-or-creates the 1:1 and posts a StatusMessage.EmergencyContactDesignated status (subject = recipient, so the sender's own copy reads "You added X…"). Mirrors the ConversationStarted send pattern; best-effort, returns the conversation id or null. Pairs with the receive-side hook in ConversationStream/AppModule and ContactRepository.setEmergencyContact. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Detail screen: - "Emergency contact" indicator under the avatar/odinId when the contact is one of our emergency contacts. - Management action toggles between "Mark as emergency contact" (sets the flag + notifies the contact via EmergencyContactDesignated) and "Remove as emergency contact" (clears it, local-only). Shown for a synced Homebase identity. - ContactDetailViewModel.handleMakeEmergencyContact / handleRemoveEmergencyContact; new actions/events + snackbars; ContactBookEntry.isEmergencyContact. API: - ContactRepository.clearEmergencyContact + setEmergencyContact refactored onto a shared writeEmergencyFlag helper over the new version-gated ContactsProvider.writeEmergencyContact. - EmergencyContactDelta/SetEmergencyContactRequest: a dedicated wire shape that always emits the bool, so a clear (isEmergencyContact=false) can be expressed — ContactContent omits a false (@EncodeDefault NEVER). Assumes the server honors an explicit false as a clear. Covered by a new clearEmergencyContact test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ShowConversationSettings picked the peer via participants.firstOrNull(), but participants is the raw recipient list that includes the owner. When the owner sorted first, tapping the 1:1 conversation header opened the owner's own contact detail instead of the person being chatted with. Exclude the owner before picking the peer, mirroring handleShowContactInfo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…you" Move the emergency-contact flag off the shared ContactContent into this app's private inline app-data slot (keyed by AppConfig.APP_ID): - New core EmergencyContact.kt: ChatContactAppData blob, Contact.isEmergencyContact(), ContactRepository.setEmergencyContact/clearEmergencyContact (clear just drops the slot — no merge-can't-express-false problem), and a derived emergencyContacts flow. - Removed ContactContent.isEmergencyContact + the dedicated delta write, the api ContactRepository emergency methods/flow, and their tests; added a core read-path test. ContactBookEntry/ContactDetail/AppModule repointed to the app-data extension. Location dashboard "who can locate you" now lists contacts marked as emergency contacts (reactive), replacing the Emergency Location Access circle members; keeps the owner-console manage link. Excludes the logged-in identity (you're not your own emergency contact), and the contact-detail mark/remove action is hidden for your own self-contact (isSelf) so it can't be flagged in the first place. Also bundles in-progress contact address/label fields (ContactContent location/phone/ email labels, ContactSaveHelper, ContactBookEntry). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…anLocate Splits the two conflated directions of the emergency-location feature. "Who can locate you" now reads our own emergency-circle membership (the source of truth) instead of an app-data flag; the per-contact flag is repurposed to the reverse direction — `iCanLocate`, "people we can locate" — and backs a new "Who you can locate" dashboard section. - EmergencyCircleNotifier: diffs emergency-circle membership (baseline-seeded to avoid login re-spam, reset on logout) and notifies peers on grant/revoke. - EmergencyContactRevoked status + sendEmergencyContactRevocation mirror the designation pair; MessageMapper renders the revoke system line. - EmergencyContactReceiveService: sets/clears iCanLocate on the receiver and silently consumes the status message (group-heal-style soft-delete) so a re-delivered designate-after-revoke can't re-flip a stale flag. Unknown senders are synced but not consumed, so the flag still lands on a later delivery. - EmergencyContactReconciler: on dashboard open, verifies each iCanLocate contact against verifyTemporalAccess and clears stale flags (lost revoke self-corrects); inconclusive checks leave the cache untouched. - Removes the contact-detail "mark as emergency contact" toggle (designation now flows from circle membership, not a button). - Moves EMERGENCY_LOCATION_CIRCLE_ID into AppConfig. Tests cover the membership-diff baseline-seed and the receive-side consume decision tables. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… show data freshness Tapping Sync on the contact detail screen now also preflights temporal read access to the contact's location drive (verifyTemporalAccess — reads no data, fires no peer notification). On access, sets the iCanLocate "emergency contact" flag (adding them to the emergency list) and surfaces the newest-file timestamp as "Location data as of <when>" under the badge. A definitive no-access clears a stale flag, mirroring EmergencyContactReconciler; network/parse failures are inconclusive and leave state untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…learer copy On Android 11+ the "Allow all the time" (background) permission can't be granted by re-firing the runtime dialog — after the first non-grant the OS silently auto-denies (a brief GrantPermissionsActivity flash), so the Setup row kept showing "Grant" (permanentlyDenied stays false in the "ask every time" state) and looped, never routing to Settings. Fix: latch alwaysRequestAttempted when the user taps Grant; once set, the row shows "Open settings" instead of re-offering Grant. The latch clears when the grant lands so a later revoke starts fresh. Also clarify the copy: rename the row to "Allow all the time" (matches Android's own label), reword the default hint, and add a settings-specific hint that tells the user to choose "Allow all the time" in system Settings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ages Disable kotlin.incremental.native: on Kotlin 2.3.21 the per-module incremental-native bitcode cache crashes the K/N backend with "Lowering ReturnsInsertion: phases [Enums] are required, but not satisfied" during :homebase-core:linkDebugFrameworkIosArm64 when an unchanged downstream module (homebase-auth) is rebuilt against changed dependencies (homebase-api/common). It's a compiler cache-codegen phase bug, not our code; the cache is build-speed only and framework output is identical with it off. MessageMapper: a soft-deleted status message (e.g. an emergency-contact designation consumed by the receiver) should render nothing, not a "Deleted File" tombstone — the user never authored or saw it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deleting a contact only removed the address-book record, leaving a connected identity's connection (and the drive access it granted) live. Delete now tears down the connection first when the contact is connected, and the confirm prompt warns about it. - handleConfirm() disconnects before delete for connected contacts, aborting (and surfacing the connection error) if disconnect fails so we never drop the record while the connection lingers. - New contactbook_detail_delete_message_connected string; ConfirmDialog shows it when isConnected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.