Merge branch 'mantra' into claude/home-chat-previews-e1913f

Two conflicts, and both are the same collision: mantra took schema v15 while
this branch was also calling its migration v15.

**The version number.** mantra's v15 adds `ChatRoom.joinedGroupAt` with a manual
MIGRATION_14_15, because half of what it does -- deleting the placeholder chat
lines already written for messages sent before this device joined -- is not a
shape Room generates. That is the older claim on the number and it keeps it. The
index migration here becomes `AutoMigration(from = 15, to = 16)` and the database
goes to v16, so a device that has already run v15 gets the index on top of it
rather than the two fighting over one version.

`15.json` is resolved to mantra's wholesale -- an add/add conflict between two
unrelated schemas is not something to merge line by line -- and 16.json is
regenerated from the build. Checked rather than assumed: 16.json differs from
15.json in exactly one place, `index_ChatMessage_chatRoomId`, and no table's
fields, createSql or other indices move.

**mantra's new membership lines needed handling here**, and nothing would have
told me: `98f766f` added `TYPE_MEMBER_INVITED`, `TYPE_MEMBER_INVITE_SENT` and
`TYPE_MEMBER_INVITE_FAILED`, which the transcript renders as system notices. The
chat list preview dispatches on the same question the transcript does -- is this
somebody's words -- and a type missing from that check falls through to the chat
bubble branch. A room whose newest line was an invite would have previewed as
"Alice: Invited Bob to the group", which reads as Alice having said it. Exactly
the failure `ChatMessage.MEMBERSHIP_TYPES`' own comment warns about, one screen
over from where it was written.

So `MEMBERSHIP_TYPES` joins the ritual and chronicle sets in
`lastChatMessagePreviewText`. They are not in the AUTHORED sets -- their content
is a whole sentence with the invitee's name already in it -- so they stand alone,
which is what the transcript does with them too. One new test, over all three
types rather than a representative one, since the set is the thing being relied
on.

Nothing else needed reconciling. mantra's pre-join fix filters at indexing time
and deletes the rows outright, so the last-message subquery sees fewer rows and
needs no `memberSince` clause of its own to stay in step with the transcript.

550 jvm tests and 319 android unit tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kgothatso NgakoandClaude Opus 5 committed 2026-09-06 20:12:49 +02:00
commit 30c732af67
24 files changed
+7054 -109

No files matched your search

@@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 15,
"identityHash": "9bf80a49771342e992ef3ad38208bf4c",
"identityHash": "513c8f6ec811352bd00aba1d34651d76",
"entities": [
{
"tableName": "BroadcastNostrEventReceipt",
@@ -390,17 +390,6 @@
"id"
]
},
"indices": [
{
"name": "index_ChatMessage_chatRoomId",
"unique": false,
"columnNames": [
"chatRoomId"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_ChatMessage_chatRoomId` ON `${TABLE_NAME}` (`chatRoomId`)"
}
],
"foreignKeys": [
{
"table": "ChatRoom",
@@ -666,7 +655,7 @@
},
{
"tableName": "ChatRoom",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `userPublicKey` TEXT NOT NULL, `subject` TEXT, `description` TEXT, `mlsGroupState` TEXT, `initialGiftWrapPayloadId` TEXT, `leftGroupAt` INTEGER, `chronicleRequestedAt` INTEGER, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`userPublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`initialGiftWrapPayloadId`) REFERENCES `GiftWrapPayload`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `userPublicKey` TEXT NOT NULL, `subject` TEXT, `description` TEXT, `mlsGroupState` TEXT, `initialGiftWrapPayloadId` TEXT, `leftGroupAt` INTEGER, `joinedGroupAt` INTEGER, `chronicleRequestedAt` INTEGER, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`userPublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`initialGiftWrapPayloadId`) REFERENCES `GiftWrapPayload`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
@@ -705,6 +694,11 @@
"columnName": "leftGroupAt",
"affinity": "INTEGER"
},
{
"fieldPath": "joinedGroupAt",
"columnName": "joinedGroupAt",
"affinity": "INTEGER"
},
{
"fieldPath": "chronicleRequestedAt",
"columnName": "chronicleRequestedAt",
@@ -5642,7 +5636,7 @@
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9bf80a49771342e992ef3ad38208bf4c')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '513c8f6ec811352bd00aba1d34651d76')"
]
}
}
File diff suppressed because it is too large. Load diff