Repository navigation
feat(chat): scaffold GetRoster and EditChat - #824
Merged
Merged
Conversation
flipcash2-protobuf-api dd5e92db -> 4ccbbe43 adds Chat.GetRoster and Chat.EditChat, plus Member.joinedAt/version and ViewerState.Permissions.can_edit. Wire the app side against the local path override, ahead of the published package bump: - ChatService: getRoster (paged) and editChat, each with an explicit Result-enum mapping (ErrorGetRoster, ErrorEditChat) rather than positional rawValue: — EditChatResponse.Result has five cases against this file's usual three, so a positional match would silently renumber if a case were inserted upstream. - FlipClient+Chat: async wrappers, including roster paging to exhaustion behind a single RosterPage read. - ConversationMember gains joinedAt/version (the GetRoster merge key); ConversationViewerState gains canEdit. - ConversationStreamEvent/Store/Controller handle the two new MetadataUpdate cases, titleChanged and pictureChanged, applied best-effort since neither carries a version to compare. - Tests for the new field mappings, event decoding, and error classification. Deliberately left unscaffolded: any can_edit-gated UI affordance, and the picture-upload flow EditChat depends on (BlobStorage upload of the original, then passing the resulting BlobId) — both are UI/flow work this pass doesn't touch.
0.11.0 isn't published yet — it ships with the GetRoster/EditChat client release. FLIPCASH_PROTO_LOCAL replaces this exact: requirement with a path dependency, so a local build ignores the pin; CI resolves it and can't yet, which is why the PR goes up as a draft.
bmc08gt
marked this pull request as ready for review
September 21, 2026 22:34
bmc08gt
added a commit
that referenced
this pull request
Sep 22, 2026
#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
added a commit
that referenced
this pull request
Sep 22, 2026
…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
added a commit
that referenced
this pull request
Sep 22, 2026
#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
added a commit
that referenced
this pull request
Sep 22, 2026
…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
added a commit
that referenced
this pull request
Sep 22, 2026
…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
added a commit
that referenced
this pull request
Sep 22, 2026
…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
added a commit
that referenced
this pull request
Sep 22, 2026
* feat(chat): edit a group's name and icon 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. * fix(store): bump schema version so viewer state is refetched #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. * feat(chat): confirm group edits, and call the row Picture 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. * docs(claude): state the schema-version rule in terms of what the store 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
added a commit
that referenced
this pull request
Sep 22, 2026
…#827) main does not build from its committed pins. `AllTargets` fails with `cannot find type 'ReportReason' in scope`, and testing is cancelled before a single test runs. #821 added the report flow against `SharedCoreKit.ReportReason` and developed it through the `FLIPCASH_SHARED_CORE_LOCAL` path dependency that the same PR introduced. `Package.resolved` was correctly restored to the pinned 0.7.0 before committing, as that manifest's comment instructs, but the shared-core release carrying the type was never cut, so nothing published provided it. shared-core 0.7.1 publishes it. `.upToNextMinor(from: "0.7.0")` already admits 0.7.1, so only the resolved pin moves and the three manifests consuming SharedCoreKit stay untouched. The same file also still pinned flipcash2-client-protocol at 0.10.0. `FlipcashAPI/Package.swift` requires the contract packages `exact:`, and #824 moved that declaration to 0.11.0 without committing the resolution it forces, so every build since has rewritten the file locally. Commit that too rather than leave the checked-in state unbuildable as written.
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.
Scaffolds the service layer for the two RPCs added in
code-payments/flipcash2-client-protocol#17, and pins the release that carries them.
Blocked on that PR. The
exact:requirement onflipcash2-client-protocolnow names0.11.0,which has no tag yet, so CI cannot resolve the package until it is published. This stays a draft
until then. The work here was developed and verified against the client repo's checkout through
FLIPCASH_PROTO_LOCAL, a shell variable CI never sees.What this adds
ChatServicegainsgetRosterandeditChat, withErrorGetRosterandErrorEditChatmappingthe result enums explicitly rather than positionally. That matters here: this codebase maps
result enums through
Error*(rawValue:), so a case inserted upstream renumbers everything afterit. Nothing was inserted this time, but the two new enums are written so a future insertion is a
compile error rather than a silent mis-mapping.
FlipClient+Chatwraps both;getRosterpages to exhaustion into a singleRosterPage.ConversationMembercarries the newjoinedAtandversion,ConversationViewerStatecarriescanEdit, andConversationStreamEvent/ConversationStore/ConversationControllerdecodeand apply the two new
MetadataUpdatecases at every existing switch site over that oneof.Two things a caller has to know
A roster page is not a snapshot. A large group's page is served from an index that trails
membership writes, and the client cannot tell that case from a small group's whole-roster read.
Pages have to be merged against the event stream per member, by
ConversationMember.version,greater winning. The types carry what that needs and the rule is in the doc comments on
FlipClient.RosterPageandChatService.getRoster, but no merge exists yet because nothingconsumes
getRoster— whoever builds the roster UI has to write it.canEditis server-computed and not derivable. Gate the edit affordance on the flag alone,not on membership or chat kind.
Deliberately not included
No
canEdit-gated UI, and no picture-upload flow —editChattakes aBlobIdfor a blob alreadyuploaded and
READYvia BlobStorage. Both are feature work on top of this scaffolding.