Merge branch 'mantra' into claude/focused-brattain-433b3b
Takes the projects line, the chapter uploads, the tablet room selection and the adoption fixes -- thirty-eight commits, 204 tests. **Renumbered, for the third time in this window.** `mantra` reached 22 with the projects tables, having itself renumbered from 21 after the parentage table took that; this branch had already written 22 and 23. So the ritual keying becomes 22->23 and the membership split 23->24, by the rule in docs/curated-to-mantra-profiles.md that a version belongs to whichever tree lands first. Nothing had shipped on either. Schema 23 had to be regenerated rather than kept: the old 22.json described `mantra@21 + the ritual keying`, and the intermediate shape is now `mantra@22 + the ritual keying`, which includes the three project tables. Exported by putting the entities briefly back to that shape, since Room only ever emits the current version. **Projects added three more cascading children, so ChatRoom now has twenty-two.** All three of `MantraProject`, `MantraProjectArtifactVersion` and `MantraProjectTranslationArtifactVersion` reference the room `ON DELETE CASCADE`. The split's migration never enumerated them -- it depends on Room disabling foreign keys, which is asserted rather than assumed -- so it needed no change, but every count in its prose did. `ChatRoomSplitThroughRoomJvmTest` is better for it: starting from a v22 file now runs both of this branch's migrations in one upgrade, which is what a device coming off the projects release will actually do. Conflicts resolved toward mantra's shape with this branch's change applied inside it: `roomDerivedBy` keeps its new extracted helper and reads the ceremony's own threshold key through `getCeremonyThresholdPublicKey`, and `ChatRoomDetailViewModel` keeps the `parents` rename with the per-profile MLS read. The new projects and chapters fixtures needed the same membership seeding as the rest. 1212 tests, 0 failures.
This commit is contained in:
commit
deaf36f688
132 files changed
+22980
-691
No files matched your search
@@ -22,6 +22,8 @@ silent, or a decision that looked arbitrary and was not.
|
||||
| [multiple-profiles.md](./multiple-profiles.md) | several profiles on one device — why a profile is a credential and a seed is a wallet attached to one, why a switch is a restart rather than a swap, the two entrances the app lacks, and the inbox a switch would silently lose |
|
||||
| [jvm-target.md](./jvm-target.md) | what desktop support cost, phased — why the native chain was already done, why an empty source set in our phoenix fork was the real blocker, and why DAO tests need none of it |
|
||||
| [npub-profile-preview.md](./npub-profile-preview.md) | showing the person before a direct message is started from a pasted npub — why the preview is a screen keyed by public key, what "found" means when a row can be a placeholder, and the phase that makes the button honest about a key package that never comes |
|
||||
| [chapter-uploads.md](./chapter-uploads.md) | picking files off a device and having the group sign each one in as a chapter — why N files can never be one signing session, and the four things the single-chapter path gets away with |
|
||||
| [project-feature.md](./project-feature.md) | bringing the server schema's `project` and its two join models into a group that has no accounts and no server — a creator tag of its own and owners as a signed set of `p` tags, the role the parser has to be told about or every project lands ownerless, why a membership is its own signed event rather than a list, and the kind band that is already double-booked |
|
||||
| [material-design-conformance.md](./material-design-conformance.md) | what the M3 foundations actually require, measured against all 43 screens — the colour pairing that renders the app's own proposals invisible, and eight phases that put the decisions back in the theme |
|
||||
| [curated-to-mantra.md](./curated-to-mantra.md) | pulling the Curated fork's thirty-nine commits back under Mantra's names — which lines of work to take, the three decisions, and a measured way to replay a twice-rebranded history without touching seven hundred files by hand |
|
||||
| [curated-to-mantra-profiles.md](./curated-to-mantra-profiles.md) | the second pull: the fork's several-profiles line, ten commits and its first schema migration — which of it is a fix Mantra has today, who allocates a schema version number when two trees share one history, and the one conflict, which was Mantra's own |
|
||||
@@ -77,3 +79,21 @@ smallest of the plans: it changes the entrance to the direct-message flow and
|
||||
nothing past it, so read it with `StartDirectMessageToNpubOrNip05Dialog` and
|
||||
`ChatRoomMessagingViewModel.initiateNewChat` open, its four decisions before its
|
||||
four phases, and its table of where the build chose differently first.
|
||||
The chapter uploads note is a phased plan that has been built, and is the
|
||||
only one that starts from another codebase: it mirrors the mantra.press web app's
|
||||
multi-file upload under this app's rule that the group, not the uploader, is the
|
||||
author. Read the batch-signing note first — its three limits are the whole shape
|
||||
of this one — and read its four-defects section even if the feature is never
|
||||
built, because each of those is wrong in the single-chapter path today. Its two
|
||||
splitter phases are separable from the rest and worth reading on their own: what
|
||||
a chunk is, measured against the parser the web app translates with rather than
|
||||
assumed.
|
||||
The projects note is a phased plan that has been built as far as its seventh
|
||||
phase, and is the only one
|
||||
whose starting point is outside this repository: the platform's server schema,
|
||||
`mantra-db-models`, has had the three models for years. It reads as the
|
||||
translation domain's unanswered question — the app can say what a chapter is and
|
||||
who translated it, and cannot say what any of it is for — so read it after the
|
||||
nip30303 conventions are familiar, its decisions before its phases, and its scope
|
||||
fork before either, because that one changes the shape of the rest, and its
|
||||
table of where the build chose differently first.
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,177 @@
|
||||
#!/usr/bin/env node
|
||||
//
|
||||
// Generates the golden fixtures for `Markdown.splitBlocks`, from markdown-it.
|
||||
//
|
||||
// `Markdown.splitBlocks` is hand-rolled, and its output is hashed into chunk
|
||||
// event ids that a quorum signs -- so it is pinned by measurement against the
|
||||
// reference implementation rather than by anybody's reading of CommonMark. This
|
||||
// script is that measurement. It runs markdown-it over the corpus below and
|
||||
// writes the expected chunks as a Kotlin source file in commonTest.
|
||||
//
|
||||
// node docs/scripts/markdown-block-fixtures.js \
|
||||
// --markdown-it <path to a markdown-it install> \
|
||||
// [--out composeApp/src/commonTest/kotlin/.../MarkdownBlockFixtures.kt]
|
||||
//
|
||||
// mantra.press has one: its node_modules/markdown-it. Pass that path.
|
||||
//
|
||||
// THE THREE DEPARTURES are applied here, in the oracle, so the Kotlin test is a
|
||||
// plain equality check and the departures are visible in one place:
|
||||
//
|
||||
// 1. A fenced or indented code block yields no chunk. markdown-it's `fence`
|
||||
// and `code_block` tokens carry content, so mantra.press turns `code();`
|
||||
// into a chunk -- with a trailing newline in its text -- and asks a
|
||||
// translator to translate it and the group to sign it as prose.
|
||||
// 2. A table is one chunk of its own source, not one per cell. Four chunks
|
||||
// for a 2x2 table is markdown-it's honest reading, but each costs an event
|
||||
// envelope to carry one word and a table's alignment is structure that
|
||||
// per-cell translation cannot preserve.
|
||||
// 3. A link reference definition yields no chunk. This one is free: it is
|
||||
// markdown-it's own behaviour with the `reference` block rule left
|
||||
// enabled. mantra.press disables it -- reaching for the wrong rule name to
|
||||
// stop links rendering -- which drops `[ref]: https://...` through to
|
||||
// `paragraph` and makes a translatable chunk of a URL.
|
||||
//
|
||||
// `html: false` is markdown-it's default and is kept: an HTML block comes back
|
||||
// as a paragraph, which is what the Kotlin subset does too.
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
function arg(name, fallback) {
|
||||
const i = process.argv.indexOf(name)
|
||||
return i >= 0 && process.argv[i + 1] ? process.argv[i + 1] : fallback
|
||||
}
|
||||
|
||||
const mdPath = arg('--markdown-it', null)
|
||||
if (!mdPath) {
|
||||
console.error('--markdown-it <path to a markdown-it install> is required')
|
||||
process.exit(2)
|
||||
}
|
||||
const outPath = arg(
|
||||
'--out',
|
||||
'composeApp/src/commonTest/kotlin/press/mantra/compose/text/MarkdownBlockFixtures.kt'
|
||||
)
|
||||
|
||||
const md = require(path.resolve(mdPath))()
|
||||
|
||||
/**
|
||||
* The corpus. Each entry is a shape that either appears in real chapters or has
|
||||
* already caught something. The converter shapes come from mantra.press's own
|
||||
* scripts/convert-btc-tex-to-markdown.js, which is what the files being
|
||||
* uploaded are made by.
|
||||
*/
|
||||
const CORPUS = {
|
||||
'one paragraph': 'A single paragraph.\n',
|
||||
'two paragraphs': 'First paragraph.\n\nSecond paragraph.\n',
|
||||
'lazy continuation': 'First line.\nSecond line of the same paragraph.\n',
|
||||
'atx heading apart': '# Chapter one\n\nThe first paragraph.\n',
|
||||
'atx heading glued': '# Chapter one\nThe first paragraph.\n',
|
||||
'deep atx heading': '#### A fourth-level heading\n\nBody.\n',
|
||||
'converter shape': '# Chapter one\nFirst paragraph here.\n\nSecond paragraph here.\n',
|
||||
'setext h1': 'Title\n=====\n\nBody.\n',
|
||||
'setext h2': 'Subtitle\n--------\n\nBody.\n',
|
||||
'tight bullet list': 'Shopping:\n\n- milk\n- bread\n- eggs\n',
|
||||
'ordered list': '1. first\n2. second\n',
|
||||
'nested list': '- outer\n - inner\n',
|
||||
'blockquote two lines': '> line one\n> line two\n',
|
||||
'two blockquotes': '> first\n\n> second\n',
|
||||
'thematic break': 'One.\n\n---\n\nTwo.\n',
|
||||
'fenced code': 'Text.\n\n```\ncode();\n```\n\nMore.\n',
|
||||
'fenced code with info': 'Text.\n\n```kotlin\nval x = 1\n```\n',
|
||||
'indented code': 'Text.\n\n indented code\n\nMore.\n',
|
||||
'table': '| a | b |\n|---|---|\n| 1 | 2 |\n',
|
||||
'table with prose': 'Before.\n\n| a | b |\n|---|---|\n| 1 | 2 |\n\nAfter.\n',
|
||||
'link reference definition': 'See [it][ref].\n\n[ref]: https://example.com\n',
|
||||
'inline markup survives': 'A *word* and a [link](https://example.com) and `code`.\n',
|
||||
'trailing hard break': 'Line one \nLine two\n',
|
||||
'no trailing newline': 'No newline at the end.',
|
||||
'leading blank lines': '\n\nA paragraph after blank lines.\n',
|
||||
'crlf already normalised': 'One.\n\nTwo.\n',
|
||||
'html block falls back to a paragraph': '<div>html block</div>\n',
|
||||
'heading then list then quote':
|
||||
'# Title\n\n- one\n- two\n\n> quoted\n\nEnd.\n',
|
||||
'multi paragraph blockquote': '> first para\n>\n> second para\n',
|
||||
'unicode': 'Ke a leboga.\n\nሰላም ለዓለም።\n',
|
||||
}
|
||||
|
||||
/** The expected chunks for one input, with the three departures applied. */
|
||||
function expected(source) {
|
||||
const lines = source.split('\n')
|
||||
const tokens = md.parse(source, {})
|
||||
const out = []
|
||||
|
||||
let tableDepth = 0
|
||||
|
||||
for (const token of tokens) {
|
||||
if (token.type === 'table_open') {
|
||||
// Departure 2: one chunk of the table's own source.
|
||||
const [start, end] = token.map
|
||||
out.push(lines.slice(start, end).join('\n'))
|
||||
tableDepth++
|
||||
continue
|
||||
}
|
||||
if (token.type === 'table_close') {
|
||||
tableDepth--
|
||||
continue
|
||||
}
|
||||
if (tableDepth > 0) continue
|
||||
|
||||
// Departure 1: code carries content and is not prose.
|
||||
if (token.type === 'fence' || token.type === 'code_block') continue
|
||||
|
||||
// Departure 3 needs no code: with the `reference` rule enabled, a link
|
||||
// reference definition produces no token at all.
|
||||
|
||||
if (token.type === 'inline' && token.content) out.push(token.content)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
function kotlinString(value) {
|
||||
return '"' + value
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/"/g, '\\"')
|
||||
.replace(/\$/g, '\\$')
|
||||
.replace(/\n/g, '\\n')
|
||||
.replace(/\r/g, '\\r')
|
||||
.replace(/\t/g, '\\t') + '"'
|
||||
}
|
||||
|
||||
const entries = Object.entries(CORPUS).map(([name, source]) => {
|
||||
const chunks = expected(source)
|
||||
return ` Fixture(
|
||||
name = ${kotlinString(name)},
|
||||
markdown = ${kotlinString(source)},
|
||||
chunks = listOf(${chunks.length === 0 ? '' : '\n' + chunks.map(c => ' ' + kotlinString(c)).join(',\n') + ',\n '}),
|
||||
),`
|
||||
}).join('\n')
|
||||
|
||||
const file = `package press.mantra.compose.text
|
||||
|
||||
// GENERATED by docs/scripts/markdown-block-fixtures.js -- do not edit by hand.
|
||||
//
|
||||
// The expected output of [Markdown.splitBlocks] for ${Object.keys(CORPUS).length} inputs, taken from
|
||||
// markdown-it with the three departures recorded in docs/chapter-uploads.md
|
||||
// applied by the generator. Regenerate with:
|
||||
//
|
||||
// node docs/scripts/markdown-block-fixtures.js \\
|
||||
// --markdown-it <path>/node_modules/markdown-it
|
||||
//
|
||||
// A change here is a change to what a chunk is, and so to the ids a quorum
|
||||
// signs. It should arrive with a reason.
|
||||
|
||||
data class Fixture(
|
||||
val name: String,
|
||||
val markdown: String,
|
||||
val chunks: List<String>,
|
||||
)
|
||||
|
||||
val MARKDOWN_BLOCK_FIXTURES: List<Fixture> = listOf(
|
||||
${entries}
|
||||
)
|
||||
`
|
||||
|
||||
fs.mkdirSync(path.dirname(outPath), { recursive: true })
|
||||
fs.writeFileSync(outPath, file)
|
||||
console.log(`wrote ${Object.keys(CORPUS).length} fixtures to ${outPath}`)
|
||||
Reference in new issue
Block a user