Curated (remote `curated`, curated/curated-kmp) forked from this repository atba26c0b1on 2026-09-09 and has landed thirty-nine commits since: a group's own nostr identity, curated lists over kinds 31888-31890, sign-in with a recovery phrase, an nsec or an npub, a back button on every pushed screen -- and two rebrands, 744 and 765 files each, underneath all of it. docs/curated-to-mantra.md is which of that Mantra wants, in what order, and how it lands under Mantra's names. docs/scripts/curated-unbrand.py is the rewrite the plan depends on, and docs/README.md gets the row. **The plan is measured, not argued, and that is its whole claim to be read.** The obvious way to write it was to reason from the commit list about what would conflict. Instead the method was run to the end on a scratch worktree before a sentence was written: Curated's history rewritten into Mantra's names, replayed onto origin/mantra with `rebase --rebase-merges`, built for both targets, and put through jvmTest, testDebugUnitTest and the m3 audit. Every number in the document is from that run -- 37 of 39 commits replay, 3 conflicts with known resolutions, 1,036 jvm tests and 530 common tests at 0 failures, all audit budgets met, Room regenerating nothing -- and the appendix records it so the next reader can tell a drifted number from a wrong method. A plan whose mechanics had not been tried would have been a list of hopes about seven hundred renamed files. **Mantra has not moved since the fork, and that decides the shape of the problem.** The merge-base of the two branches is origin/mantra itself, so `git merge curated/curated` is a fast-forward: it merges nothing and makes Mantra become Curare, icon, package rename and the deletion of Mantra's own library, dialects and projects sections included. The document says so before anything else because it is the one thing git does by default. It also means the pull is not a merge at all but a translation -- a Curated commit written in Mantra's vocabulary applies to Mantra as if it had been written there -- which is why a history rewrite followed by an ordinary rebase works, and cherry-picking the original commits (every path and every import line in every context wrong) does not. **808a3459, which drops the translation sections, is dropped rather than reverted afterwards, because that was measured too.** The first plan was to replay everything and restore the sections with a forward commit written against the final row layout. Trying the drop instead showed git merging 930d37c8, 1e52fc8f and 55664cc7's rewrite of ChatRoomDetailScreen around it without a conflict: the screen comes out as signing key, the five identity rows, Propose event, then Library, Dialects, Projects, then Subgroups, with `artifacts` and `dialects` still on the UI state, and the same 1,036 tests pass -- GroupSignedWorkRowsJvmTest only asserts that Subgroups is below the block. The one conflict is a modify/delete on a test 55664cc7 deletes anyway. A restore commit would have been Mantra owning a layout upstream never had. **Curated lists are pulled although they are not Mantra's product, and the reasoning is written out in full because it is the only real product question.** Excluding the five commits is not deleting five commits: the paste flow routes kind 31889 beside 0, 1 and 10002, broadcast seeds an entry's relays from its schema, the row restructure builds five rows of which two are these, the back-button sweep touches the suggestion screens, and 165 of the 306 strings added since the fork are theirs, interleaved. Carving that out means editing five other commits by hand and then owning a group screen that conflicts with upstream's on every future pull. Including it costs no schema, no migration and no query on a screen nobody opens. The principle underneath is stated as such: the method only stays cheap while Mantra's tree is a superset of Curated's non-brand tree, so every feature left out is a permanent seam. If product wants the two rows gone, that is two lines behind a constant, not surgery. **The brand line lands as one re-messaged commit of comments, and Torch is retired natively first.** Run through the rewrite, the two rebrand commits collapse from 744 and 765 files to sixteen and twenty-one, and what survives is the reasoning the rebrands added about the names they refused to change -- the comments on TWEAK_TAG, HOST_KEY_DERIVATION_TAG and Relays.ephemeral and the paragraph in shared-key-derivation.md. Mantra should have those; the next person to grep "mantra/" here is as likely to finish the rename as anyone upstream was. The rewritten d26cf6c7 is a different case: Mantra is still Torch in TorchTheme (116 references), UserAgent, two error strings, and an iOS config that builds Torch.app under a bundle id from two brands ago. That is Mantra's own debt and Phase 0 pays it as MantraTheme and "Mantra", so that CurareTheme maps onto a name that means something rather than onto the brand before last. Then d26cf6c7' is dropped in the replay, since its work is done -- the plan was made to say this explicitly after Phase 0 and Phase 2 were found to disagree about it. **The normaliser is a script in docs/scripts rather than a description, because three of its rules were wrong before they were right and prose would have hidden that.** `\b` does not fire inside snake_case, so `\bcurare\b` misses the string keys that carry the brand and they need look-around rules that treat `_` as a boundary. File names carry identifiers, so a path rule that only moves directories leaves MantraNavHost.kt and CurareNavHost.kt side by side with equal content. And "Curated" is two words -- the brand in generation one, the protocol's name at the tip (CuratedSchemaEvent, nostr/curated/, the queue's "Curated" mark) -- so the bare word is deliberately not a rule and only the generation-one identifiers are mapped, which is exact because the two commits written in that generation never use the word as a brand in code. Every rule matches a brand token and none matches a Mantra one, so the twelve Mantra* entities, the two mantra/ prefixes and ephemeral.mantra.press cannot be touched by construction; the check is that the entity-token count does not fall (433 before, 435 after, none lost) and that the rewritten tip compiles, which no missed identifier would survive. It has a tree mode for `filter-branch --tree-filter` and a patch mode for `format-patch` files; the consolidated script was re-run on a fresh export of the tip and reproduces the tree that was built and tested, to the byte, logo aside. **The phases are cuts through the graph, not lines of work, so no commit is replayed twice and the merges are recreated where they were.** The natural grouping is by feature line, but the lines interleave -- the nsec branch and the queue fork at the same commit and rejoin at c8de3a1f, the back-button branch joins the accept flow at fedbe724 -- and two of those merges carry hand resolutions that a flattened replay would silently lose. Each phase therefore replays every commit reachable from its cut that the previous phase did not, `--onto <mantra tip> <previous cut> <this cut>`, and the two conflicts and two reconcile files that the dry run found sit in Phase 4 with their resolutions written down as a runbook. The exactness check closes each phase: the diff between the replay and the rewritten tip must be exactly what was chosen to drop, and anything else is a rule that is wrong or a resolution that is, found there rather than in production. **Phase 6 says what the fork should become, because otherwise this document is needed again next week.** Every future upstream commit is written in to.curare.*, and each pull costs the dry run, the review and the prose -- affordable once, corrosive weekly. The options are given in the order they should be taken: converge the source package (a Kotlin package is not a brand, and the rebrands' own messages say the domain is Mantra* everywhere that matters), then invert the relationship so Curated is Mantra plus a thin brand-and-product series, and only failing both keep the normaliser maintained beside the code it maps. Two things already flow the other way and are named:39fb64b6, since Curated still carries the dead WalletManagerExtension.kt and one caller, and Phase 0's Torch retirement. **Several counts in the draft were checked against the data and corrected before they were committed.** The entity-token figure quoted from the rebrand's message (1,369) was measured with a different pattern and is replaced by this run's own (433 -> 435); "roughly 150 of 293" strings became 165 of 306; the back-button commit touches eight of Mantra's translation screens, not nineteen; and the claim that nothing in the fork added a DAO method was too strong -- three queries were added, and the statement now says no table and no migration, with 19.json byte-identical after a build as the evidence. The dry run, for the record: normaliser at the tip 812 files rewritten and 806 paths moved; filter-branch over 39 commits in 84 s; replay with the logo and 808a3459 dropped 37 commits, 3 conflicts, 2 reconcile files, residual diff exactly the logo plus the three files 808a3459 touched; :composeApp:compileDebugKotlinAndroid and :composeApp:compileKotlinJvm clean; :composeApp:jvmTest 1,036 tests, 0 failures (Mantra alone: 733); :composeApp:testDebugUnitTest 530 tests, 0 failures; :composeApp:m3Audit all budgets met with 12 adaptive uses and 2 navigation components. The scratch worktree and the tmp/ branches were removed afterwards; the script recreates them in under two minutes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This is a Kotlin Multiplatform project targeting Android, iOS, Desktop (JVM).
-
/composeApp is for code that will be shared across your Compose Multiplatform applications. It contains several subfolders:
- commonMain is for code that’s common for all targets.
- Other folders are for Kotlin code that will be compiled for only the platform indicated in the folder name. For example, if you want to use Apple’s CoreCrypto for the iOS part of your Kotlin app, the iosMain folder would be the right place for such calls. Similarly, if you want to edit the Desktop (JVM) specific part, the jvmMain folder is the appropriate location.
-
/iosApp contains iOS applications. Even if you’re sharing your UI with Compose Multiplatform, you need this entry point for your iOS app. This is also where you should add SwiftUI code for your project.
Build and Run Android Application
To build and run the development version of the Android app, use the run configuration from the run widget in your IDE’s toolbar or build it directly from the terminal:
- on macOS/Linux
./gradlew :composeApp:assembleDebug - on Windows
.\gradlew.bat :composeApp:assembleDebug
Build and Run Desktop (JVM) Application
To build and run the development version of the desktop app, use the run configuration from the run widget in your IDE’s toolbar or run it directly from the terminal:
- on macOS/Linux
./gradlew :composeApp:run - on Windows
.\gradlew.bat :composeApp:run
Build and Run iOS Application
To build and run the development version of the iOS app, use the run configuration from the run widget in your IDE’s toolbar or open the /iosApp directory in Xcode and run it from there.
Learn more about Kotlin Multiplatform…