Kgothatso Ngako 2f8df359a8 feat(chapters): upload several files, one proposal each
Phase 4 of docs/chapter-uploads.md: the feature.

A review step rather than a submit button, and that is the one place this
departs visibly from the web app it mirrors. mantra.press can post twelve
files and redirect, because what it creates belongs to the uploader and the
uploader can fix it. Here each file becomes a request for the group's own
signature, so a filename standing in for a title, a file that decoded to
nothing, or a chapter too long to carry is wrong in something a quorum is
about to put its name to. Picking files produces a list; the list is what is
proposed.

Per-row problems, because a bad file should not cost the other eleven: not
text, too large, nothing to translate, no name, more chunks than a session
signs, more bytes than a proposal carries -- and a duplicate, which warns
rather than blocks. A book can repeat a page, but the common cause is picking
the same directory twice, and two chapters with the same text are two event
ids nothing later can merge.

Proposals go out sequentially, never concurrently: each writes a session and
publishes group events, and a group event that arrives a moment out of order
is dropped for good (docs/mls-skipped-keys.md). Indices are allocated once
from a freshly read base, so numbering does not depend on how fast the
sessions are written.

Partial success is the expected outcome and not an error. Nothing can retract
a proposal, so if the fourth fails the first three are real and the group can
still sign them: no rollback, no navigating away, the rows that went are
marked and the rest keep their reason, which is the only place a retry can be
aimed from. A pass that fully succeeds lands on the proposal list rather than
a signing screen -- at N there is no one session to watch.

Two shapes the writing settled. UploadReview holds the row building and the
problem decisions, which are pure functions of the files, the names typed
over them and the chapters the version already has; the view model keeps only
the orchestration, and the 13 tests need no view-model harness. And uiState
is a StateFlow, not `by mutableStateOf`: initiate() writes from
Dispatchers.IO after a primary-key lookup, which is exactly the few
milliseconds in which a write from another thread into a composition's
snapshot is silently discarded -- the bug that once left this app on
"Decrypting..." for ever.

Scaffolding into translations is not here yet; Phase 5 lifts it out of
AddChapterViewModel so one batch covers every chapter rather than one session
each. Until then an uploaded chapter reaches a translation through that
translation's own catch-up.

m3Audit's budgets all met.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-02 19:12:34 +02:00
2026-09-09 17:04:09 +02:00
2026-03-24 04:47:19 +02:00
2026-03-23 01:41:39 +02:00
2026-03-23 01:41:39 +02:00
2026-03-23 01:41:39 +02:00

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…

Description
Mantra as a kotlin multiplatform project
https://mantra.press
Readme 13 MiB
Languages
Kotlin 100%