Repository navigation
feat(chat): edit a group's name and picture - #825
Merged
Merged
Conversation
Puts an overflow menu on the chat profile screen with a single Edit item, gated on the server-computed `canEdit` permission and nothing else, and pushes a list with two rows behind it: Icon and Name. The design (node 10187:110373) draws four rows. Only Icon has contract support — `EditChatRequest` carries a title and a picture, so Membership Card, Description and Social Links would be rows that cannot save. Name is the counterpart the design omits, styled to match Icon. Both rows are partial updates. `EditChat` treats an unset field as unchanged, so each screen submits only its own field, which is what keeps a rename from clearing the picture. The title goes through the existing `GroupTitleValidator` for the contract's 1...64 before it is sent, and the picture waits for the blob to reach READY — a pending blob is answered `PICTURE_BLOB_NOT_ACCEPTED`. On OK the response metadata is applied through the same store mutators the `titleChanged` and `pictureChanged` stream events use, rather than through `.metadataRefresh`. Both are last-write-wins assignments, so the server's echo re-applies identical values instead of landing the edit twice, and the held membership and viewer state survive — a wholesale upsert would drop them. Error copy mirrors the StartChat mapping case for case, so the same server answer reads the same way wherever a group name is submitted. A moderated title keeps the user on the edit screen with the field as they typed it.
#824 added a non-optional `canEdit` to `ConversationViewerState` without bumping the schema version. A blob written by an earlier build has no `canEdit` key, so decoding throws `keyNotFound`; the read site uses `try?`, so the whole viewer state degrades to nil. That loses mute state as well as the new permission, and it persists until something refetches the chat. Observed on a simulator upgraded across #824: every conversation held `{"version":N}` with no `canEdit`, and `Conversation.canEdit` read false for two groups the signed-in user created. After the bump the store rebuilt and the same two rows came back `{"canEdit":true,...}`. A rebuild costs a resync and loses nothing: every column in the affected tables is server-derived, and drafts live in a separate file. That is the project's stated substitute for migrations, so use it rather than teaching the decoder to tolerate the older shape.
bmc08gt
force-pushed
the
feat/edit-chat-ui
branch
2 times, most recently
from
September 22, 2026 17:23
784bc06 to
ce9eda2
Compare
Saving a group's name or picture changes it for everyone in the group, so neither leaf screen sends on the first tap any more: Save opens a confirmation alert and the alert commits, the way the My Account fields already work. Both take the red banner — where the profile confirmations weigh whether the user can get their own old value back, this edit lands on everyone at once and no later undo takes back what they have seen. The row is Picture, not Icon, which is what `EditChatRequest.picture` carries and what Android ships. Its glyphs come from Android too — `photo` and `textformat` against `Icons.Outlined.Image` and `Icons.Outlined.TextFields` — so the screen reads the same on both platforms. They need `resizable()` to hold the 26pt column: `textformat` is wider than tall, and a frame alone left it overflowing and the labels misaligned. The name screen loses its bar title. The field it opens on is the name, so a title only repeated it back.
…e writes The rule named `SQLiteVersion` in Info.plist, which moved into code as `Database.schemaVersion` when the notification service extension needed the same number. It also only covered table definitions, so #824 read as exempt: it added a non-optional property to a `Codable` persisted as a JSON blob, leaving the column's SQL type untouched while making every row written by an earlier build fail to decode. Because those reads are `try?`, the whole viewer state silently became nil — losing mute state, not just the new field. Both facts are now in the rule, along with why a tolerant decoder is the wrong fix: it keeps a stale value where the bump refetches the correct one, and it is a migration under another name.
bmc08gt
force-pushed
the
feat/edit-chat-ui
branch
from
September 22, 2026 17:37
ce9eda2 to
685a8ba
Compare
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.
Adds the group-chat Edit UI: an overflow menu on the chat profile screen, and a two-row list behind it for the group's Picture and Name.
The gate
The menu item appears only while
ViewerState.Permissions.canEditis true.Conversation.canEditforwards that field and consults nothing else — not membership, not creator identity, not chat kind. The server computes the permission from the viewer's standing against the chat's rules, so any client-side conjunct would either offer an affordance the server will deny or hide one it would have allowed. Absent viewer state means not permitted.If the permission is withdrawn while the list is open, the screen unwinds rather than leaving the user on rows that can no longer save.
Saving asks first
Both fields change the group for everyone in it, so Save proposes and a confirmation alert commits — the pattern the My Account fields already use. Both take the red banner: the profile confirmations weigh whether the user can get their own old value back, while this edit lands on everyone at once and no later undo takes back what they have already seen.
Partial updates
EditChattreats an unset field as unchanged, so each screen sends only its own field and a rename cannot clear the picture.EditGroupModel.saveTitlepassespictureBlobID: nilby construction andsavePicturepassestitle: nil; both are covered by tests rather than left to inspection.The title is validated through the existing
GroupTitleValidatorfor the contract's 1...64 before it is sent. The picture is stored, awaited to READY, and only then submitted — a pending or processing blob is answeredPICTURE_BLOB_NOT_ACCEPTED. A failed edit keeps the blob reservation so a retry does not upload a second copy; picking a different picture drops it.Not double-applying the echo
On
OKthe response metadata is applied throughConversationStore.applyTitleChangedandapplyPictureChanged— the same mutators thetitleChangedandpictureChangedstream events use — rather than through.metadataRefresh.Two reasons. Both mutators are last-write-wins assignments, so the server's echo of this edit re-applies identical values instead of landing it twice. And
.metadataRefreshupserts the whole conversation: the edit response carries chat metadata, not the locally-held membership flag, viewer state or roster, so a wholesale replace would drop them. Nothing is refetched.A schema bump, and the rule that missed it
Database.schemaVersiongoes to 39. #824 addedcanEdittoConversationViewerStateas a non-optionalBool, so a viewer-state blob written before it throwskeyNotFound; the read istry?, which turns that into a silent nil for the whole value — losing mute state, not just the new permission. Without the bump, a chat the user created readscanEdit: falseand the menu never appears.The hard rule named
SQLiteVersionin Info.plist, which has since moved into code, and covered only table definitions, which this is not. Both are fixed here, along with why a tolerant decoder is the wrong answer: it preserves a stale value where the bump fetches the correct one, and it is a migration under another name.Error presentation
The mapping mirrors
NewPublicGroupScreen's case for case, including log messages and dialog copy, so the same server answer reads the same way wherever a group name is submitted.TITLE_MODERATEDkeeps the user on the edit screen with the field as they typed it, since amending it there is the only way forward.