feat(discussions): questions on published studysets, answered by learners

A learner asks about a published studyset, a topic in it, or one card,
note or step of a release, and others answer, as on Stack Exchange
(ADR 0013): questions and answers in the app's editor without images,
sounds or videos, comments of up to 600 characters under the question
and each answer. There are no votes: the asker accepts an answer, which
comes first, then the oldest; the author's posts are marked. The
instance reads from git's file list which topics a question's target
sits in, so a topic lists what is asked within it and below it.

Posts live in two server-only tables, written through endpoints, so the
synced tables stay as they are and v0.17.0 keeps syncing. Everyone
signed in reads; writing needs an identity and a public profile; the
studyset's author hides posts and blocks people with feedback's blocks;
a withdrawn studyset takes no new questions; anyone writes at most 30
posts an hour. A post with others' replies under it is emptied when
deleted, else deleted; deletePerson deletes what a person wrote the same
way, and a published studyset's deletion takes its discussions with it.

The migration adds Serverpod's gen_random_uuid_v7(), discussion_post
and discussion_seen with their indexes and cascading foreign keys, and
changes nothing else.

In the app, discussions open from a copy's studyset or topic menu, a
studied card's "Ask about this", Explore's summary and the Publish
sheet; the Profile tab's inbox lists those the person follows, and its
count includes what is new in them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-15 23:52:40 +02:00
commit 5efa552489
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -1749,11 +1749,78 @@
"feedbackUnreachable": "Meldungen und Vorschläge sind gerade nicht erreichbar.",
"feedbackRetry": "Erneut versuchen",
"inboxTitle": "Posteingang",
"inboxIntro": "Meldungen und Vorschläge zu deinen veröffentlichten Lernsets und die, die du geschrieben hast.",
"inboxIntro": "Fragen und Antworten, Meldungen und Vorschläge zu deinen veröffentlichten Lernsets und die, in denen du geschrieben hast.",
"inboxEmpty": "Noch nichts.",
"studysetFeedback": "Meldungen und Vorschläge",
"studysetFeedbackEmpty": "Noch hat niemand etwas gemeldet oder vorgeschlagen.",
"reportToAuthor": "Fehler im Original melden",
"discussionsTitle": "Diskussionen",
"discussionsSearch": "Fragen durchsuchen",
"discussionsAsk": "Frage stellen",
"discussionsEmpty": "Noch keine Fragen",
"discussionsEmptyHint": "Stell die erste: zum Lernset, zu einem Thema oder zu einer seiner Karteikarten.",
"discussionsNoMatch": "Kein Fragentitel sagt das.",
"discussionsMore": "Mehr zeigen",
"discussionsUnreachable": "Die Diskussionen sind gerade nicht erreichbar.",
"discussionsAnswers": "{count, plural, =0{Keine Antworten} =1{1 Antwort} other{{count} Antworten}}",
"@discussionsAnswers": {"placeholders": {"count": {"type": "int"}}},
"discussionsBy": "{name} · {date}",
"@discussionsBy": {"placeholders": {"name": {"type": "String"}, "date": {"type": "String"}}},
"discussionsSomeone": "Jemand",
"discussionsDeletedQuestion": "Gelöschte Frage",
"discussionsHidden": "Ausgeblendet",
"discussionsAboutCard": "Zu einer Karteikarte",
"discussionsAboutNote": "Zu einer Notiz",
"discussionsAboutStep": "Zu einem Schritt",
"discussionsAccepted": "Angenommen",
"discussionsOn": "Zu {title}",
"@discussionsOn": {"placeholders": {"title": {"type": "String"}}},
"discussionRefusalNoIdentity": "Um zu fragen und zu antworten, melde dich im Profil mit einer Identität an: Beiträge zeigen, wer sie geschrieben hat.",
"discussionRefusalNoProfile": "Um zu fragen und zu antworten, leg im Profil ein öffentliches Profil an: Beiträge zeigen, wer sie geschrieben hat.",
"discussionRefusalBlocked": "Wer dieses Lernset veröffentlicht hat, lässt dich zu seinen Lernsets nichts schreiben.",
"discussionRefusalWithdrawn": "Dieses Lernset wurde aus Entdecken genommen: Was hier steht, bleibt, aber Neues wird nicht gefragt.",
"discussionRefusalNotInRelease": "Das ist nicht mehr in der Veröffentlichung des Lernsets.",
"discussionRefusalEmpty": "Schreib zuerst etwas.",
"discussionRefusalTooLong": "Das ist länger als erlaubt.",
"discussionRefusalTooMany": "Du hast in der letzten Stunde viel geschrieben. Versuch es etwas später noch einmal.",
"discussionRefusalWithFiles": "Bilder und Töne lassen sich nicht posten: Deine Dateien gehören dir allein, also sähe sie niemand sonst.",
"discussionFailed": "Die Instanz ist nicht erreichbar. Versuch es noch einmal, sobald du online bist.",
"discussionTitle": "Frage",
"discussionMissing": "Diese Frage gibt es nicht mehr.",
"discussionDeleted": "Das wurde gelöscht.",
"discussionEdited": "bearbeitet",
"discussionWriteAnswer": "Antwort schreiben",
"discussionYourAnswer": "Deine Antwort",
"discussionEditHeading": "Bearbeiten",
"discussionTitleField": "Titel",
"discussionTitleHint": "Was möchtest du wissen?",
"discussionPost": "Posten",
"discussionComment": "Kommentar hinzufügen",
"discussionCommentHint": "Eine kurze Bemerkung",
"discussionAccept": "Annehmen",
"discussionUnaccept": "Zurücknehmen",
"discussionMenu": "Mehr",
"discussionMenuEdit": "Bearbeiten",
"discussionMenuDelete": "Löschen",
"discussionMenuHide": "Ausblenden",
"discussionMenuShow": "Wieder zeigen",
"discussionMenuBlock": "{name} blockieren",
"@discussionMenuBlock": {"placeholders": {"name": {"type": "String"}}},
"discussionDeleteTitle": "Das löschen?",
"discussionDeleteBody": "Was andere darunter geantwortet haben, bleibt, ohne deinen Text und Namen.",
"discussionAnswered": "Antwort gepostet.",
"discussionCommented": "Kommentar gepostet.",
"discussionSaved": "Gespeichert.",
"discussionDeletedDone": "Gelöscht.",
"discussionAcceptedDone": "Antwort angenommen.",
"discussionUnacceptedDone": "Jetzt ist keine Antwort angenommen.",
"discussionHiddenDone": "Ausgeblendet: Nur wer es geschrieben hat und du sehen es.",
"discussionShownDone": "Wieder sichtbar.",
"discussionBlockedDone": "Blockiert: Was diese Person zu deinen Lernsets schreibt, sieht nur sie selbst.",
"discussionAskAbout": "Dazu fragen",
"inboxDiscussions": "Diskussionen",
"inboxFeedback": "Meldungen und Vorschläge",
"inboxDiscussionsEmpty": "Noch keine Diskussionen.",
"cardReportToAuthor": "Auch im Original melden",
"cardReportToAuthorHint": "Öffentlich: alle, die es lernen, lesen es, mit deinem öffentlichen Profil.",
"feedbackReportSent": "Im Original gemeldet.",