Deck opslaan overschrijft externe wijzigingen zonder conflictcontrole #1699
Labels
No labels
accepted
bug
declined
docs
duplicate
enhancement
good first issue
in-progress
needs-info
privacy
security
triage
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
LibreKAT/Ocideck#1699
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Probleem
Een presentatie opslaan schrijft de markdown onvoorwaardelijk over het pad heen. Heeft een andere editor, een synchronisatiemap of een git-actie het bestand sinds het openen gewijzigd, dan verdwijnt die wijziging zonder melding.
Dit is de presentatiekant van #1683 (hetzelfde gat in de documentmodus). Apart gehouden omdat de reparatie hier ander materiaal heeft — zie hieronder: de vingerafdruk bestáát al.
Reproductie
.mdbuiten de app (andere editor,git checkout, een synchronisatiemap).Verwacht
Vóór het overschrijven wordt gecontroleerd of het bestand nog overeenkomt met wat er bij het openen (of bij de vorige opslag) stond. Bij een verschil krijgt de gebruiker de keuze: vergelijken, herladen, of bewust overschrijven.
Technische aanwijzing
FileService._writeProject(lib/services/file/file_service_project.dart) doetwriteStringAtomic(File(filePath), markdown)zonder voorafgaande lezing of vergelijking. Alle drie de opslagroutes (saveDeck,saveDeckDetailed,saveDeckAsDetailed) lopen daarlangs.Wat er al ligt
De vingerafdruk bestaat al.
Deck.fileHash(SHA-512 over de bestandsbytes) wordt gezet bij het openen (FileService.openDeck…, viaDocumentIntegrity.hashMarkdown(raw)) én na elke schrijfactie (DocumentIntegrity.recordWrittenBytes). Er ontbreekt alleen de vergelijking vóór het schrijven — dit is geen nieuw mechanisme, maar één controle op een waarde die er al is.Het patroon staat ook al in de repo.
_writeChartData(lib/services/file/file_service_open.dart) leest een gekoppelddata/*.jsonterug, vergelijkt het met de basislijn van bij het openen en weigert mét waarschuwing bij een verschil. Wat daar geldt voor de cijfers van één grafiek, geldt zeker voor het deck zelf.De git-route heeft het wél.
tabs_provider_git.dartkentGitSaveStatus.conflictmet een echte samenvoeging. Het gat zit in de gewone lokale-bestandsroute.Oplossingsrichting
_writeProject, vóór de schrijfactie: bestaat het bestand, en isdeck.fileHashniet leeg, lees dan de bytes en vergelijk de hash. Verschil ⇒ niet schrijven, maar een conflict-uitkomst teruggeven — nietfalse, want dat leest als een schrijffout.DeckNotifier._saveToPath) biedt drie wegen: vergelijken, herladen (eigen wijzigingen weg), overschrijven (die van de ander weg). Nooit stil één daarvan kiezen.Drie valkuilen die in het werk horen
fileHash= geen controle. Bij een deck uit geheugen of uit het web (content != null) wordt de hash bij het openen niet berekend. Geen vingerafdruk betekent niets te vergelijken; dan schrijven zonder klagen, niet weigeren.Opslaan alsnaar een nieuw pad is nooit een conflict — de vergelijking geldt alleen voor het pad waar de hash vandaan komt. Bestaat het doelbestand al, dan is dat de gewone overschrijfvraag van de bestandskiezer, een ander gesprek.DeckNotifier._saveToPathzet de teruggegeven deck (met versefileHash) niet in de state wanneer de gebruiker tijdens het schrijven doortypte (deuserEdited-tak, #1473). De volgende opslag zou dan een conflict melden tegen het bestand dat de app zélf zojuist schreef. De hash moet ook in die tak worden bijgewerkt — een valse conflictmelding is precies wat een gebruiker deze controle laat wantrouwen..mdschrijft_writeProjectnotities, MIAUW, zegel en dismissals. De controle op de.mddekt die niet. Voor deze ronde: benoem het, dek de.mdaf, en laat de sidecars een vervolgvraag zijn — een half werkende controle die doet alsof ze alles dekt is erger dan geen.Regressietest
Kosten
Opslaglaag +
DeckNotifier+ dialoog, gedeeld met #1683. De dialoog kost ongeveer vijf nieuwel10n.d('…')⇒ 31 vertalingen elk (make add-l10n). Reken op een dag voor beide kanten samen; los is dit ongeveer een halve.Gevonden bij de triage van #1683 (22-08-2026), geverifieerd tegen
main(e93ef205c).