Skip to content

agent: #11 [Agent] Konfliktkopien von iCloud/OneDrive und doppelte Kapitel-IDs erkennen - #20

Merged
leonatwork merged 1 commit into
mainfrom
agent/bearbeite-github-issue-11-vollst-ndig-un-1020-1119
Oct 20, 2026
Merged

leonatwork merged 1 commit into
mainfrom
agent/bearbeite-github-issue-11-vollst-ndig-un-1020-1119

Conversation

@leonatwork

@leonatwork leonatwork commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

Zusammenfassung

Autonome Umsetzung von #11: [Agent] Konfliktkopien von iCloud/OneDrive und doppelte Kapitel-IDs erkennen

Verifikation

  • Coder-/Reviewer-Loop: APPROVE nach 3 Runde(n)

  • Tests und Qualitätsprüfungen: siehe Commit und CI-Checks

  • Kein automatischer Merge oder Produktionsdeploy

  • Modell: azure/gpt-6.1-sol; Decision-1 (active): route gpt-5.3-codex, Risiko medium; QS: npm run build && npm test bestanden

  • Art der Änderung (Triage): code; Zweitprüfung: azure/gpt-5.3-codex

  • Kosten (Listenpreis): 5,0 Mio Tokens · 2,33 €

Closes #11


Note

Medium Risk
Changes core project-load semantics (missing/conflicted chapters may lack daten.kapitel entries), which downstream UI and sync must handle; behavior is heavily tested but touches the persistence read path.

Overview
Adds runtime manuscript conflict detection (Issue #11, package 1): duplicate chapter IDs, filename vs frontmatter ID mismatches, missing files, iCloud placeholders, and heuristic cloud copy names (Finder/OneDrive patterns) are reported in DE/EN and prevent loading the affected chapter text while keeping order and plans.

ladeProjektMitBericht now exposes konflikte and stores the same list on ProjektDaten.manuskriptKonflikte (not persisted). Chapters flagged as not loaded no longer get synthetic empty kapitel entries; explicitly planned chapters without a file still behave as before. External file sync (uebernehmeExterneAenderungen) refreshes conflict state and respects the same rules.

The Tauri layer lists .<id>.md.icloud placeholders but returns None on read so core can diagnose without reading placeholder content. Coverage includes new Vitest cases, stricter import guards for the read-only konflikte module, and a flaky app test fix that waits for the lazy-loaded writing area.

Reviewed by Cursor Bugbot for commit 9d378ea. Bugbot is set up for automated code reviews on this repo. Configure here.

…bare Lösung.

Lies zuerst den vollständigen Issue-Thread inklusive aller Kommentare, CLAUDE.md und die relevanten Repository-Regeln. Prüfe vorhandene PRs und aktuellen Code, bevor du Änderungen machst. Leite die verbindlichen Akzeptanzkriterien aus dem Issue ab und dokumentiere nicht erfüllbare oder widersprüchliche Kriterien klar.

Arbeite testgetrieben: Erzeuge Regressionstests, verifiziere ihr korrektes Fehlschlagen vor dem Fix und führe danach die relevanten Tests sowie die vorgeschriebenen Qualitätsprüfungen aus. Ändere nur, was für das Issue erforderlich ist. Arbeite ausschließlich auf dem ausgecheckten Stand von origin/main: checke keine Branches fremder offener PRs aus, cherry-picke nichts daraus und baue nicht auf ihnen auf. Hängt die Lösung von einem noch offenen PR ab, liefere nur deinen eigenen Anteil und dokumentiere die Abhängigkeit im Handover. Keine Produktionsdeployments, keine Änderungen an Produktionsdaten oder Produktions-Supabase und kein --prod. Merge den PR nicht.

Issue: #11
Titel: [Agent] Konfliktkopien von iCloud/OneDrive und doppelte Kapitel-IDs erkennen

Beschreibung:
<!-- hermes-agent-form-v1:start -->
## Ziel und Nutzen
Konfliktkopien, doppelte Kapitel-IDs und nicht geladene iCloud-Platzhalter in manuskript/ erkennen und als strukturiertes Ergebnis melden. Das ist Paket 1 von 3 und Grundlage für Schreibsperre und UI.

## Ist-Zustand / Problem
Bei doppelter ID gewinnt still die alphabetisch spätere Datei. Fehlende oder ausgelagerte Kapitel erscheinen als leerer, beschreibbarer Text. Externe Kopien werden still ignoriert oder als neues Kapitel angehängt. .icloud-Platzhalter sind in Rust nicht erkennbar.

## Umfang (In Scope)
- Erkennung in core/projekt beim Laden (ladeProjekt) und bei externen Änderungen (uebernehmeExterneAenderungen) für manuskript/
- Fälle: doppelte id, Name ungleich id, fehlende Datei oder .icloud-Platzhalter, typische Konfliktnamen als Hinweis
- Strukturiertes Konfliktergebnis neben den Ladewarnungen, Meldungstexte in DE und EN
- Rust: .icloud-Platzhalter in der Dateiliste erkennbar, nicht als Projektdatei behandelt
- Vitest mit MemoryFs und Rust-Unit-Test

## Nicht Teil dieses Issues (Out of Scope)
- Schreibsperre (Paket 2)
- Kapitelliste-Markierung und Konfliktdialog mit Wort-Diff (Paket 3)
- Automatisches Zusammenführen und Auflösen per Knopf
- Herunterladen ausgelagerter iCloud-Dateien
- Konflikte in anderen Ordnern als manuskript/
- Änderungen am Dateiformat oder an E1
- Externe Sicherung

## Fachliche und UX-Entscheidungen
- Geklärt: Dateiname = <id>.md (E1), eine Abweichung ist ein Fehlerzustand
- Geklärt: Atomares Speichern (E9) bleibt unverändert
- Geklärt (Leon): Aufteilung in 3 Pakete: Erkennung, Schreibsperre, UI
- Geklärt (Leon): Bei doppelter ID nur melden und sperren, nicht laden
- Annahme: Nur betroffene Kapitel werden gesperrt (Paket 2)
- Offen (Leon, außerhalb dieses Issues): Auflösen-Funktionen

## Prüfbare Akzeptanzkriterien
- AC-1: Wenn zwei Dateien in `manuskript/` dieselbe `id` haben, dann meldet das Laden einen Konflikt mit beiden Pfaden. Ein strukturiertes Konfliktergebnis liegt neben den Ladewarnungen (Markierung in der Kapitelliste folgt in Paket 3).
- AC-3: Wenn `manuskript/x.md` im Frontmatter `id: y` hat, dann wird die Abweichung gemeldet.
- AC-4: Wenn ein Kapitel aus `projekt.md` nur als `.<id>.md.icloud` vorliegt, dann wird es als „nicht geladen“ mit Erklärung gemeldet und nicht als leeres Kapitel geladen.
- AC-5: Taucht während der Sitzung eine Konfliktkopie auf, dann meldet `uebernehmeExterneAenderungen` den Hinweis ohne Neustart, und die Kopie wird nicht als neues Kapitel angehängt.
- AC-6: Bei doppelter ID wird keine der beiden Dateien als Kapiteltext geladen, und das Ergebnis nennt beide Pfade.
- AC-7: Rust erkennt `.icloud`-Platzhalternamen in `dateien.rs` (Unit-Test), ohne sie als Projektdatei zu behandeln.
- AC-8: Ein Projekt ohne Konflikte lädt wie bisher. Die bestehenden Tests in `core/projekt` und `abnahme/` sowie `npm test`, `npm run typecheck` und `npm run build` bleiben grün.

## QS und Nachweis
- Vitest in core/projekt neben projekt.test.ts und extern.test.ts mit MemoryFs: doppelte ID, Name ungleich ID, Platzhalter, Kopie ohne id, Konfliktkopie während der Sitzung, Projekt ohne Konflikt unverändert
- Rust-Unit-Test für .icloud-Namen in src-tauri/src/dateien.rs (läuft in der macOS-CI)
- npm test, npm run typecheck, npm run build grün; npm run sperrliste vor dem Commit
- Commits mit Sign-off (git commit -s)
- Manuell auf dem Mac (nach Paket 3): iCloud- und OneDrive-Konfliktkopie, ausgelagerte Datei

## Offene Fragen
–

_Automatisch nach dem Formular „Agent-Auftrag“ strukturiert (Hermes-Triage). Die ursprüngliche Beschreibung steht unten unverändert._
<!-- hermes-agent-form-v1:end -->

<details><summary>Ursprüngliche Beschreibung</summary>

<!-- hermes-original:start -->
### Ziel und Nutzen

Als Autorin mit dem Projektordner in iCloud oder OneDrive möchte ich, dass Plotwerk Konfliktkopien, doppelte Kapitel-IDs und nicht geladene Cloud-Platzhalter erkennt und mir zeigt, damit kein Text unbemerkt verschwindet oder überschrieben wird.

### Ist-Zustand / Problem

- Das README empfiehlt iCloud und OneDrive als Ablage (`README.md:33-34`), das Handbuch ebenso (`docs/handbuch.md:4`).
- `parseKapitel` nimmt die `id` aus dem Frontmatter und erst danach den Dateinamen (`core/projekt/lesen.ts:345-348`).
- `ordner()` legt Kapitel nach `id` ab. Bei gleicher `id` überschreibt die später gelistete Datei still die frühere (`core/projekt/lesen.ts:1244-1250`, Zeile 1248). Die Liste ist alphabetisch sortiert (`src-tauri/src/dateien.rs:201`). Welche Fassung gewinnt, hängt also vom Dateinamen ab. Die andere ist in der App unsichtbar.
- Fehlt die Datei eines Kapitels aus `projekt.md`, lädt `ladeProjekt` es als leeren Text (`core/projekt/lesen.ts:1311`). Das betrifft auch ausgelagerte iCloud-Dateien: Der Platzhalter `.<name>.md.icloud` endet nicht auf `.md` und wird übergangen.
- `ist_hilfsdatei` kennt nur `.DS_Store` und eigene Temp-Dateien, keine `.icloud`-Platzhalter (`src-tauri/src/dateien.rs:16-19`).
- Änderungen von außen ordnet `uebernehmeExterneAenderungen` nach Dateiname zu (`idAusPfad`, `core/projekt/extern.ts:66-70`), das Laden dagegen nach Frontmatter-`id`. Eine Kopie ohne `id` wird als neues Kapitel hinten angehängt (`core/projekt/extern.ts:80-84`). Eine Kopie mit gleicher `id` wird still ignoriert.
- Gespeichert wird immer nach `manuskript/<id>.md` (`core/projekt/lesen.ts:120`).

Reproduktion (doppelte ID):
1. Demo-Projekt mit `manuskript/grundriss.md` (`id: grundriss`) öffnen und schließen.
2. Daneben `manuskript/grundriss 2.md` mit gleichem Frontmatter und anderem Text anlegen, so wie iCloud eine Konfliktkopie benennt.
3. Projekt wieder öffnen.

Erwartet: Ein Hinweis nennt beide Dateien mit derselben ID. In diese Dateien wird nichts gespeichert, bis der Konflikt geklärt ist.
Tatsächlich: Nur eine Fassung ist sichtbar, kein Hinweis. Tippen speichert nach `grundriss.md`.

Reproduktion (Platzhalter):
1. Projekt in iCloud Drive, `manuskript/grundriss.md` nur als `.grundriss.md.icloud` vorhanden.
2. Projekt öffnen.

Erwartet: Das Kapitel ist als „nicht geladen“ markiert.
Tatsächlich: Das Kapitel erscheint leer. Wer tippt, legt eine neue `grundriss.md` an, die mit der Cloud-Fassung kollidiert.

### Umfang (In Scope)

- Erkennung in `core/projekt` beim Laden und bei Änderungen von außen, für `manuskript/`:
  - mehrere Dateien mit derselben Kapitel-`id`,
  - Dateiname passt nicht zur Frontmatter-`id`,
  - Kapitel aus `projekt.md`, dessen Datei fehlt oder nur als `.icloud`-Platzhalter existiert,
  - typische Konfliktnamen (`<name> 2.md` von iCloud, `<name>-<Gerät>.md` von OneDrive) als Hinweis.
- Strukturiertes Konfliktergebnis neben den bestehenden Ladewarnungen, Texte in DE und EN.
- Schreibsperre für betroffene Kapitel: kein Autosave und kein sonstiges Speichern in eine Datei, die im Konflikt steht oder als Platzhalter nicht geladen ist.
- Markierung in der Kapitelliste und ein Konfliktdialog: beteiligte Dateien und ein Diff der Fassungen mit der vorhandenen Vergleichsansicht (`src/features/versionen/Spaltenvergleich.tsx`, `wortDiff` in `core/versionen/index.ts:145`).
- Rust: `.icloud`-Platzhalter in der Dateiliste erkennbar machen, ohne sie als Projektdatei zu behandeln.

### Nicht Teil dieses Issues (Out of Scope)

- Automatisches Zusammenführen von Fassungen.
- Auflösen per Knopf, solange Leon das nicht entschieden hat (siehe Offen).
- Herunterladen ausgelagerter iCloud-Dateien anstoßen.
- Konflikte in anderen Ordnern (`figuren/`, `orte/`, `plan/kapitel/` …). Die Erkennung soll sich aber später dafür wiederverwenden lassen.
- Änderungen am Dateiformat oder an E1.
- Externe Sicherung (siehe Issue zur automatischen Sicherung außerhalb des Projektordners).

### Fachliche und UX-Entscheidungen

- Geklärt (Code): E1 legt fest: Dateiname = `<id>.md`, die ID ändert sich nie (`docs/architektur.md:11`). Eine Abweichung zwischen Dateiname und ID ist daher immer ein Fehlerzustand.
- Geklärt (Code): E8 legt vor dem Überschreiben einer von außen geänderten Fassung schon einen Schnappschuss an (`extern`). Das schützt nicht davor, dass die falsche Fassung geladen wurde.
- Geklärt (Code): Atomares Speichern (E9) bleibt unverändert.
- Annahme: Gesperrt werden nur die betroffenen Kapitel. Der Rest des Projekts bleibt bearbeitbar.
- Annahme: Bei doppelter ID zeigt die App die Datei `<id>.md` an, meldet die Kopie und lädt sie nicht als eigenes Kapitel.
- Annahme: Neuere macOS-Versionen lagern Dateien teils ohne sichtbaren `.icloud`-Platzhalter aus. Erkannt wird hier nur, was im Dateisystem sichtbar ist.
- Migration: Keine Datenänderung. Projekte ohne Konflikt verhalten sich wie bisher.
- Fehlerfall: Der Konfliktdialog lässt sich schließen. Das Kapitel bleibt dann gesperrt und markiert.
- Offen (Leon): Nur blockieren und erklären, oder auch auflösen, z. B. „diese Fassung behalten“, „Kopie als neues Kapitel übernehmen“, „Kopie verwerfen“, jeweils mit Schnappschuss?

### Prüfbare Akzeptanzkriterien

AC-1: Wenn zwei Dateien in `manuskript/` dieselbe `id` haben, dann meldet das Laden einen Konflikt mit beiden Pfaden, und die Kapitelliste markiert das Kapitel.
AC-2: In die Datei eines Kapitels im Konflikt schreibt die App nichts, auch nicht per Autosave, bis der Konflikt behoben ist. Ein Test mit einem Dateisystem, das Schreibvorgänge zählt, belegt das.
AC-3: Wenn `manuskript/x.md` im Frontmatter `id: y` hat, dann wird die Abweichung gemeldet.
AC-4: Wenn ein Kapitel aus `projekt.md` nur als `.<id>.md.icloud` vorliegt, dann erscheint es als „nicht geladen“ mit Erklärung und nicht als leeres, beschreibbares Kapitel.
AC-5: Taucht während der Sitzung eine Konfliktkopie auf, dann erscheint der Hinweis ohne Neustart, und die Kopie wird nicht als neues Kapitel angehängt.
AC-6: Der Konfliktdialog zeigt den Wort-Diff der beiden Fassungen, in DE und EN.
AC-7: Ein Projekt ohne Konflikte lädt und speichert wie bisher. Die bestehenden Tests in `core/projekt` und `abnahme/` bleiben grün.

### QS und Nachweis

- Agent: Vitest in `core/projekt` neben `projekt.test.ts` und `extern.test.ts`, mit `MemoryFs`: doppelte ID, Name ungleich ID, Platzhalter, Kopie ohne `id`, Konfliktkopie während der Sitzung, Schreibsperre. UI-Test für Markierung und Dialog. Rust-Unit-Test in `src-tauri/src/dateien.rs` für `.icloud`-Namen; `cargo test` läuft in der CI auf macOS. `npm test`, `npm run typecheck` und `npm run build` grün.
- Manuell auf dem Mac: Projekt in iCloud Drive mit echter Konfliktkopie (zwei Geräte oder Kopie im Finder) und mit ausgelagerter Datei („Mac-Speicher optimieren“). Zusätzlich eine OneDrive-Konfliktkopie.

### Technischer Kontext und Referenzen

- Relevante Dateien: `core/projekt/lesen.ts`, `core/projekt/extern.ts`, `src-tauri/src/dateien.rs`, `src-tauri/src/ueberwachung.rs`, `src/state/store.ts` (`persistiere`), `src/features/versionen/Spaltenvergleich.tsx`, `src/features/schreiben/VersionenDialog.tsx`, `src/features/schreiben/KapitelListe.tsx`.
- Architektur: E1, E8, E9 in `docs/architektur.md`.
- Verwandt: Issue zur automatischen Sicherung außerhalb des Projektordners.

### Sicherheits- und Liefergrenzen

- [X] Das Issue enthält keine Secrets, Tokens, Zugangsdaten oder vertraulichen Manuskript- und Kundendaten.
- [X] Der Agent darf Code und Tests ändern sowie einen Draft-PR öffnen, aber nicht selbst mergen, releasen oder deployen.
- [X] Änderungen an produktiver Infrastruktur oder veröffentlichten Releases sind nicht Teil dieses Auftrags.

_Angelegt von Claude Code im Auftrag von @leonatwork auf Basis einer Code-Analyse (Stand 2026-10-20)._
<!-- hermes-original:end -->

</details>

Zusätzliche Repo-Regeln:
Commits tragen ein DCO-Sign-off (das macht dev-loop automatisch). Auf diesem Server gibt es kein Rust/Tauri: nur TypeScript-Teil (core/, src/) mit npm run build und npm test prüfen, src-tauri nicht bauen. Keine Inhalte aus privaten Manuskripten; Beispiele nur aus fixtures/. Farben nur über Tokens in src/theme/tokens.css, UI nach docs/design/screens, core/ bleibt reines TypeScript, core/ki nutzt nur ProjektLeser.

Reviewer verdict: APPROVE after 3 round(s). Generated by dev-loop (not pushed).

Signed-off-by: leonatwork <leon.11@live.de>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9d378ea. Configure here.

Comment thread core/projekt/extern.ts
let d: ProjektDaten = alt;
const kapitelBetroffen = new Set<Id>();
if (pfade.some(istManuskriptPfad) || pfade.includes(PFADE.projekt)) {
d = { ...d, manuskriptKonflikte: neu.manuskriptKonflikte ?? [] };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale conflicts unload restored chapters

Medium Severity

manuskriptKonflikte refreshes only for manuscript paths or projekt.md, but nichtGeladeneKapitel still drops those IDs on every merge. A rewritten chapter or a plan-status change that clears datei_fehlt can vanish from memory when an unrelated file event arrives.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9d378ea. Configure here.

@leonatwork
leonatwork merged commit 8eee912 into main Oct 20, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Agent] Konfliktkopien von iCloud/OneDrive und doppelte Kapitel-IDs erkennen

1 participant