feat(paket b): Umbenennen erreicht alle — PATCH /title und RENAMED im Feed (D85)

Backend: PATCH /documents/{id}/title mit expectedVersion (409 bei
veralteter Version, 400 bei leerem/zu langem Titel), neuer ChangeType
RENAMED (strukturell, immer Meilenstein), der Feed stellt den neuen Titel
im Klartext zu (ChangeEvent.title). Unter derselben Stripe-Sperre wie die
Inhalts-Patches; Owner-Vormerkung in der API-Beschreibung. Vier neue
Cucumber-Szenarien, zwei Unit-Tests.

Frontend: Der Zeilen-Stift eines Server-Dokuments benennt über den Server
um (optimistisch, 409-Retry, Rücknahme + Warnung bei Fehlschlag); fremde
Umbenennungen kommen als RENAMED über den Feed in Chip und Menü.
URL-Dokumente verlieren den Stift — ihr Name ist die URL.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-08-27 08:13:25 +02:00
co-authored by Claude Fable 5
parent c438a88344
commit fbe9b1fd22
16 changed files with 380 additions and 12 deletions
@@ -205,6 +205,45 @@ class LiveEditingStepDefinitions {
feed.content shouldBe erwartet
}
@Wenn("das Dokument auf {string} umbenannt wird")
fun `das Dokument wird umbenannt`(titel: String) =
sendRename(titel, currentDocument().version)
@Wenn("das Dokument mit veralteter Version auf {string} umbenannt wird")
fun `veraltete Umbenennung`(titel: String) =
sendRename(titel, currentDocument().version - 1)
private fun sendRename(titel: String, version: Long) {
lastResponse = client.patch()
.uri("/api/v1/documents/$documentId/title")
.contentType(MediaType.APPLICATION_JSON)
.body("""{"title":${json(titel)},"expectedVersion":$version}""")
.exchange()
.returnResult(String::class.java)
}
@Dann("erhalte ich für das Umbenennen den Status {int}")
fun `status des Umbenennens`(erwartet: Int) {
status() shouldBe erwartet
}
@Und("das Dokument trägt den Titel {string}")
fun `dokument traegt den Titel`(titel: String) {
currentDocument().title shouldBe titel
}
@Und("das Dokument steht auf Version {long}")
fun `dokument steht auf Version`(version: Long) {
currentDocument().version shouldBe version
}
@Und("das RENAMED-Ereignis nennt den Titel {string}")
fun `renamed nennt den Titel`(titel: String) {
lastBody<ChangeFeed>().events
.first { it.changeType.value == "RENAMED" }
.title shouldBe titel
}
@Und("der Feed meldet das Ereignis {string}")
fun `der Feed meldet das Ereignis`(typ: String) {
lastBody<ChangeFeed>().events.map { it.changeType.value } shouldContain typ
@@ -112,6 +112,35 @@ class DocumentServiceTest {
entry.captured.version shouldBe 4
}
@Test
fun `rename inkrementiert Version und schreibt RENAMED-Meilenstein`() {
val doc = sampleDocument(version = 3)
every { repository.findById(doc.id) } returns doc
val saved = slot<Document>()
every { repository.save(capture(saved)) } answers { saved.captured }
val entry = captureAppended()
val result = service.rename(doc.id, title = "Team-Plan", expectedVersion = 3)
result.version shouldBe 4
result.title shouldBe "Team-Plan"
result.content shouldBe doc.content /* der Inhalt bleibt */
entry.captured.changeType shouldBe ChangeType.RENAMED
entry.captured.milestone shouldBe true /* strukturell, nie bloss Sync */
entry.captured.title shouldBe "Team-Plan"
}
@Test
fun `rename mit veralteter Version wirft Konflikt und schreibt nichts`() {
val doc = sampleDocument(version = 3)
every { repository.findById(doc.id) } returns doc
shouldThrow<DocumentConflictException> {
service.rename(doc.id, title = "Zu spaet", expectedVersion = 2)
}
verify(exactly = 0) { repository.save(any()) }
}
@Test
fun `delete entfernt Dokument und schreibt DELETED-Tombstone`() {
val doc = sampleDocument(version = 2)
@@ -0,0 +1,51 @@
# language: de
Funktionalität: Ein geteiltes Dokument umbenennen
Als Bearbeiter eines geteilten Plans
möchte ich den Titel für alle ändern,
damit jeder im Wähler denselben Namen sieht.
Grundlage: Der Titel ist ein Metadatum und bekommt seinen eigenen Weg mit
Versionsprüfung (D76/D85); der Änderungsfeed stellt die Umbenennung samt
neuem Titel zu.
Szenario: Umbenennen mit aktueller Version gilt und lässt den Inhalt stehen
Angenommen es existiert ein Dokument "Plan" mit den Zeilen:
"""
- [ ] Eins
"""
Wenn das Dokument auf "Team-Plan" umbenannt wird
Dann erhalte ich für das Umbenennen den Status 200
Und das Dokument trägt den Titel "Team-Plan"
Und das Dokument steht auf Version 2
Und das Dokument hat die Zeilen:
"""
- [ ] Eins
"""
Szenario: Der Feed stellt die Umbenennung samt neuem Titel zu
Angenommen es existiert ein Dokument "Plan" mit den Zeilen:
"""
- [ ] Eins
"""
Wenn das Dokument auf "Team-Plan" umbenannt wird
Und ich die Änderungen seit Version 1 abrufe
Dann der Feed meldet das Ereignis "RENAMED"
Und das RENAMED-Ereignis nennt den Titel "Team-Plan"
Szenario: Eine veraltete Version wird abgelehnt
Angenommen es existiert ein Dokument "Plan" mit den Zeilen:
"""
- [ ] Eins
"""
Wenn das Dokument mit veralteter Version auf "Zu spät" umbenannt wird
Dann erhalte ich für das Umbenennen den Status 409
Und das Dokument trägt den Titel "Plan"
Szenario: Ein leerer Titel wird abgelehnt
Angenommen es existiert ein Dokument "Plan" mit den Zeilen:
"""
- [ ] Eins
"""
Wenn das Dokument auf " " umbenannt wird
Dann erhalte ich für das Umbenennen den Status 400
Und das Dokument trägt den Titel "Plan"